[Hóng hớt hacking #1]: Đấm lệch giá token ERC777 trong UniswapV1 — Reentrancy Attack

TH13🇻🇳
5 min readJan 5, 2023

--

Đấm lệch giá token ERC777 trong UniswapV1 — Reentrancy Attack

Vào một ngày down trend đẹp trời của đầu năm 2023, trong lúc đi lượm nhặt kiến thức thì mình đọc được 1 bài từ 2019 của OpenZeppelin:

Đọc rồi phân tích thì thấy đây là 1 case study khá hay sử dụng reentrancy đấm lệch pool khi swap token (Uniswap v1).

Giới thiệu

Reentrancy là gì?

Nói đơn giản thì re-entrancy là attacker có thể hook vào luồng thực thi của function để call lại function đó hoặc function khác, mục tiêu là có lợi cho attacker.

Mô tả trường hợp phổ biến nhất của re-entrancy

UniswapV1

Uniswap là một open-source protocol dùng để swap giữa các token. Tại thời điểm viết bài, Uniswap đang ở v3, phạm vi bài sẽ phân tích lỗi tồn tại ở v1.

UniswapV1 sẽ chia mỗi token là mỗi exchange contract. Nó chỉ thực hiện tính toán và swap giữa token và ETH (khác với giữa cặp token từ v2 trở đi).

Phân tích

Lướt trên mặt nước

Hơi bất ngờ với UniswapV1 là được code bằng vyper, nhưng may syntax cũng không khó, chỉ mất vài phút làm quen.

Theo bài viết gốc thì tác giả xác định điều kiện khai thác diễn ra khi UniswapV1 launch exchange contract với token không phải đơn thuần là ERC20 mà là ERC20 biến thể giống với ERC777.

Flow của contract dẫn tới bug:

Stack trace

Source uniswap_exchange.vy: https://github.com/Uniswap/v1-contracts/blob/c10c08d81d6114f694baa8bd32f555a40f6264da/contracts/uniswap_exchange.vy

Ví dụ về implementation của ERC777: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.3.0/contracts/token/ERC777/ERC777.sol

Độc lạ ERC777

swap token to ETH

Chuyện cũng bình thường khi swap 1 token ra ETH sẽ có các bước như sau:

  1. Tính toán lượng ETH out. (L#285)
  2. Chuyển lượng ETH out từ contract cho người nhận. (L#288)
  3. Chuyển lượng Token in từ người gửi vào contract. (L#289)

Đấy là một ngày bình thường ở ERC20, nhưng một nơi nào đó sử dụng token implement của chuẩn ERC777 thì nó lạ lắm. Flow hoạt động của ERC777 có 1 tính năng rất đặc biệt trong transfer đó là hook:

  1. Gọi đến 1 hàm của người gửi ← hook ở đây.
  2. Thực hiện transfer (thực hiện cộng trừ balances và allowances).
  3. Gọi đến 1 hàm của người nhận.

Giả sử một kịch bản tấn công: Attacker gọi tokenToEthSwapInput để swap 1 lượng x token tạm gọi là A sang ETH.

  1. Tính toán lượng ETH out.
  2. Chuyển lượng ETH out cho người nhận.
  3. Gọi đến hàm của người gửi (attacker).
  4. Thực hiện update thông số transfer.
  5. Gọi đến hàm của người nhận (người nhận ở đây là exchange contract nên bỏ qua).

Spot the bug

Sau khi trình bày 1 flow hoạt động hoàn chỉnh của contract, vậy câu hỏi đặt ra là bug nằm ở đâu? Tại sao lại bug?

Công thức toán lượng ETH out sẽ được tính như sau:

Công thức tính amount out
  • ao: amount out — lượng ETH sẽ nhận được
  • ai: amount in — lượng Token X muốn quy đổi
  • ro: reserve out — lượng ETH dự trữ (balance của ETH của contract)
  • ri: reserve in — lượng Token X dữ trữ (balance của Token của contract)

Tại bước 1, tính toán ETH out sẽ diễn ra bình thường rồi bước 2 chuyển lượng ETH đó cho người nhận cũng bình thường. →lượng ETH giảm

Đến bước gọi 1 hàm của attacker, đây là step diễn ra trước khi thực hiện transfer (update các thông số balance). Tức là hiện tại balance của Token vẫn đang giữ nguyên. → lượng Token dữ trữ không thay đổi

Vậy sẽ như thế nào khi attacker hook ở bước 3, attacker 1 lần nữa quay lại (reentrancy) gọi hàm để swap token sang ETH? Nếu bình thường thì ro giảm và ri tăng nên làm cho ao giảm 1 lượng đúng. Nhưng vì attacker hook trước khi update balance của Token trong contract nên ro giảm và ri không thay đổi → Điều này làm cho lượng ETH mình nhận sẽ cao hơn so với lượng ETH đáng lẽ được nhận.

flow attack

Như vậy, vòng lặp sẽ diễn ra số lần chỉ định (tránh vòng lặp vô hạn), đến cuối cùng thì balance Token của contract mới được update. Lúc này là đã quá muộn, attacker đã cuỗm đi 1 lượng ETH lớn hơn đáng kể so với lượng ETH bình thường nhận được. Minh họa cụ thể bằng bảng (cóp nhặt từ bài viết gốc):

Lượng ETH khai thác được so với swap ETH bình thường

Quay lại với kịch bản tấn công trên, đổi 1 lượng x token A sang ETH, lúc này mình sẽ nhận 1 lượng ETH cao hơn so với bình thường, đem lượng ETH đó đi swap về token A sẽ thu được y token. Lợi nhuận sau khi attack sẽ là y — x.

Kết luận

Đây là 1 case study khá đỉnh để bắt đầu tìm hiểu về reentrancy attack và một vài thứ hay ho như uniswap, erc20, erc777,…

Để hiểu hơn và khai thác được bug trên thì cần đi sâu vào đọc thêm code, đặc biệt là 1 số implement trong code. Điều kiện khai thác là bạn có thể hook vào flow chương trình mà mình chưa nói kĩ ở trên, đây xem như là một challenge nhỏ dành cho bạn đọc nhé 😉. PoC exploit:

Mình cảm ơn các bạn đã đọc đến đây. Là lần đầu tiên mình viết blog phân tích bug trong smart contract, rất mong sẽ nhận được thêm nhiều đóng góp < 3. Thân ái./.

--

--