시작하기
클라우드 자동 동기화

정수 오버플로우 버그를 이용한 자산 탈취 수법과 안전 수학 라이브러리

2월 06, 2026 · 2 min

증상 진단: 예상치 못한 자산 감소 또는 변조

블록체인 기반의 디파이(DeFi) 프로토콜이나 게임 내에서, 사용자의 자산(토큰, 코인, NFT)이 갑자기 사라지거나 극도로 낮은 가격에 매각된 기록이 발견되나요? 혹은 스마트 컨트랙트의 특정 함수 호출 후 프로토콜의 예치금(reserve)이 비정상적으로 감소했다는 리포트를 받았나요, 이는 단순한 해킹이 아닌, 정수 오버플로우(integer overflow) 버그를 교묘히 이용한 공격의 징후일 수 있습니다.

금융 데이터 보안 위협을 상징하는 이미지로, 금이 간 돼지 저금통에서 쏟아져 나온 동전과 금괴가 오류를 나타내는 픽셀화된 형태로 변하며 사라지는 모습을 묘사합니다.

원인 분석: 컴퓨터의 수학적 한계가 만든 취약점

스마트 컨트랙트를 비롯한 대부분의 소프트웨어는 변수가 저장할 수 있는 숫자의 크기에 한계가 있습니다, 예를 들어, `uint256`(부호 없는 256비트 정수)은 0부터 (2^256 – 1)까지의 값을 저장할 수 있습니다. 정수 오버플로우는 이 최대값을 초과하는 연산이 발생했을 때, 값이 의도치 않게 ‘한 바퀴 돌아’ 가장 작은 값(0 또는 음수)으로 변하는 현상입니다. 공격자는 컨트랙트 로직 내에서 사용자 잔고 검증, 토큰 발행량 계산, 교환 비율 산정 등에 사용되는 곱셈 또는 덧셈 연산을 악용하여, 거대한 가상의 자산을 생성하거나 상대방의 자산을 0으로 만듭니다.

해결 방법 1: 기본적인 안전 검사(SafeMath) 패턴 적용

Solidity 0.8.0 이전 버전에서는 오버플로우/언더플로우에 대한 기본적인 방어 메커니즘이 없었습니다. 따라서 모든 산술 연산에 안전 검사 로직을 직접 추가해야 했으며, 이는 OpenZeppelin의 SafeMath 라이브러리를 사용하는 것이 표준이었습니다.

  1. 의존성 추가: 프로젝트에 OpenZeppelin Contracts 라이브러리를 설치합니다. (예: npm install @openzeppelin/contracts)

  2. 라이브러리 임포트 및 적용: 컨트랙트에서 SafeMath를 임포트하고, 특정 데이터 타입(예: uint256)에 대해 사용을 선언합니다.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.7.6; // 0.8 미만 버전 가정
    
    import "@openzeppelin/contracts/utils/math/SafeMath.sol";
    
    contract VulnerableToken {
        using SafeMath for uint256; // uint256 타입에 SafeMath 메서드 적용
        mapping(address => uint256) private _balances;
    
    function transfer(address to, uint256 amount) public {
            // SafeMath의 sub() 함수는 잔고 부족 시 자동으로 revert
            _balances[msg.sender] = _balances[msg.sender].sub(amount);
            _balances[to] = _balances[to].add(amount); // add() 함수로 안전한 덧셈
        }
    }
  3. 핵심 함수 교체: 기존의 `+`, `-`, `*`, `/` 연산자를 SafeMath의 `add()`, `sub()`, `mul()`, `div()` 함수 호출로 일괄 교체합니다. 이 함수들은 오버플로우 발생 시 트랜잭션을 자동으로 되돌립니다(revert).

해결 방법 2: Solidity 0.8.0 이상 버전으로 마이그레이션 및 내장 방어 활용

Solidity 0.8.0 버전부터는 언어 차원에서 기본적인 오버플로우/언더플로우 검사가 도입되었습니다. 검사 실패 시 트랜잭션은 자동으로 revert됩니다. 이는 가장 근본적인 해결책입니다.

  1. 컴파일러 버전 업그레이드: 컨트랙트 파일 프래그마(pragma) 지시자를 `^0.8.0` 이상으로 변경합니다. (예: pragma solidity ^0.8.19;)

  2. SafeMath 사용 제거: 기본 연산자(`+`, `-`)를 그대로 사용해도 안전합니다. 기존의 `using SafeMath for uint256;` 선언과 `.add()`, `.sub()` 형태의 호출을 제거하고 일반 연산자로 교체할 수 있습니다.

  3. 언체크드(unchecked) 블록을 통한 가스 최적화: 내장 검사로 인한 가스 비용 증가가 우려되는, 오버플로우가 절대 발생하지 않음을 개발자가 보장할 수 있는 루프 카운터 등 특정 연산에는 `unchecked` 블록을 사용해 검사를 생략하고 가스를 절약할 수 있습니다.

    // Solidity 0.8.0+ 예시
    function batchProcess(uint256 iterations) public {
        // 이 루프는 iterations 수가 적당히 제한되어 있어 오버플로우 위험이 없음
        for (uint256 i = 0; i < iterations; ) {
            // ... 일부 처리 로직 ... unchecked {
                ++i; // unchecked 블록 내에서는 오버플로우 검사 없이 증가시킴
            }
        }
    }

해결 방법 3: 포괄적인 안전 수학 라이브러리 구현 및 고급 패턴

표준 연산 외에도 제곱근, 지수 연산, 백분율 계산, 고정 소수점 연산 등 복잡한 수학 연산이 필요한 경우, 검증된 안전 수학 라이브러리의 사용이 필수입니다. 이는 단순 오버플로우 방지를 넘어 수학적 정확성을 보장합니다.

OpenZeppelin의 고급 수학 유틸리티 활용

OpenZeppelin은 `Math`와 `SignedMath` 라이브러리를 제공하여 최대/최소 값 찾기, 평균 계산, 제곱근 연산 등을 안전하게 수행할 수 있게 합니다.

  1. Math 라이브러리 임포트: import "@openzeppelin/contracts/utils/math/Math.sol";

  2. 안전한 최대값 및 제곱근 계산:

    pragma solidity ^0.8.19;
    
    import "@openzeppelin/contracts/utils/math/Math.sol";
    
    contract AdvancedMathExample {
        using Math for uint256;
    
    // 두 값 중 안전하게 최대값 반환
        function max(uint256 a, uint256 b) public pure returns (uint256) {
            return Math.max(a, b);
        }
    
    // 뉴턴-랩슨 법을 이용한 안전한 제곱근 계산 (소수점 버림)
        function sqrt(uint256 a) public pure returns (uint256) {
            return Math.sqrt(a);
        }
    
    // 가중 평균 계산 시 오버플로우 방지를 위한 곱셈/나눗셈
        function weightedAverage(uint256 a, uint256 weightA, uint256 b, uint256 weightB) public pure returns (uint256) {
            // (a * weightA + b * weightB) / (weightA + weightB)
            // 곱셈과 덧셈에서 모두 오버플로우 가능성이 있음. // Math.mulDiv() 또는 별도의 안전한 수식 전개가 필요. // 실제 구현에는 보다 정교한 로직이 요구됨, }
    }

abdk 라이브러리를 이용한 고정 소수점 연산

defi에서 정확한 이자율, 환율, 기하평균 계산을 위해 널리 사용됩니다.

  1. 의존성 설치: npm install abdk-libraries-solidity

  2. 고정 소수점 연산 구현:

    pragma solidity ^0.8.19;
    
    import "abdk-libraries-solidity/ABDKMath64x64.sol";
    
    contract FixedPointExample {
        using ABDKMath64x64 for int128; // 64.64비트 고정 소수점 타입
    
    // 1.5 (고정 소수점: 1.5 * 2^64)와 2.0을 곱하여 3.0 얻기
        function multiplyFixed() public pure returns (int128) {
            int128 fixedOneAndHalf = ABDKMath64x64.fromInt(1).add(ABDKMath64x64.fromInt(1).div(2));
            int128 fixedTwo = ABDKMath64x64.fromInt(2);
            return fixedOneAndHalf.mul(fixedTwo); // 결과는 고정 소수점 3.0
        }
    
    // 자산 수량 * 가격 (소수점 포함) 계산
        function calculateValue(uint256 assetAmount, int128 priceFixedPoint) public pure returns (int128) {
            int128 amountFixed = ABDKMath64x64.fromUInt(assetAmount);
            return amountFixed.mul(priceFixedPoint);
        }
    }

주의사항 및 예방 조치

라이브러리 적용만으로 모든 위험이 사라지는 것은 아닙니다. 보안은 체계적인 프로세스입니다.

  • 컴파일러 버전 명시의 중요성: 프래그마(pragma)를 ^0.8.0과 같이 유연하게 설정하면, 미래의 주요 버전 변경 시 예상치 못한 동작이 발생할 수 있습니다. 가능하면 특정 패치 버전(예: 0.8.19)을 고정하는 것이 더 안전합니다.
  • 외부 라이브러리 검증: OpenZeppelin과 같이 오랜 기간 검증된 라이브러리를 사용하십시오. 무분별한 GitHub 복사본은 악성 코드가 삽입되었을 수 있습니다.
  • 테스트의 완결성: 단위 테스트(Unit Test)에서 오버플로우가 발생할 수 있는 경계값(0, 최대값(uint256.max), 매우 큰 값)을 반드시 포함시켜 로직을 검증해야 합니다.
  • 감사(Audit) 필수: 자체 개발한 복잡한 수학 로직이나 주요 금융 기능이 포함된 컨트랙트는 전문 보안 감사 회사를 통해 반드시 독립적인 검수를 받아야 합니다.

전문가 팁: 재진입 공격(Reentrancy)과의 혼동 주의
자산 변조 버그의 원인을 분석할 때, 정수 오버플로우와 재진입 공격을 구분해야 합니다. 오버플로우는 단일 트랜잭션 내의 '계산 오류'로 인해 발생하는 반면, 재진입 공격은 외부 컨트랙트 호출 후 상태 변경 전에 악성 컨트랙트가 함수를 반복 호출하는 '로직 흐름 제어' 공격입니다. 두 공격의 증상(자산 감소)은 유사할 수 있으나, 방어 메커니즘은 완전히 다릅니다. 체크-이펙트-상호작용(Checks-Effects-Interactions) 패턴 적용과 재진입 방지 뮤텍스(Reentrancy Guard) 사용이 후자에 대한 해결책입니다. 문제 진단 시 트랜잭션 해시를 이더스캔 같은 탐색기에서 상세히 분석하여, 정확한 원인을 규명한 후에 적합한 패치를 적용하십시오.