본문으로 건너뛰기

슬리더를 사용하여 스마트 컨트랙트 버그를 찾는 방법

Solidity
스마트 컨트랙트
보안
테스트
고급
Trailofbits
2020년 6월 9일
14 분 소요

슬리더 사용 방법

이 튜토리얼의 목적은 슬리더를 사용하여 스마트 컨트랙트에서 자동으로 버그를 찾는 방법을 보여주는 것입니다.

설치

슬리더는 Python 3.6 이상이 필요합니다. pip를 통해 설치하거나 Docker를 사용하여 설치할 수 있습니다.

pip를 통한 슬리더 설치:

pip3 install --user slither-analyzer

Docker를 통한 슬리더 설치:

docker pull trailofbits/eth-security-toolbox
docker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox

마지막 명령은 현재 디렉터리에 액세스할 수 있는 Docker에서 eth-security-toolbox를 실행합니다. 호스트에서 파일을 변경하고 Docker의 파일에서 도구를 실행할 수 있습니다.

Docker 내부에서 다음을 실행합니다:

solc-select 0.5.11
cd /home/trufflecon/

스크립트 실행

Python 3으로 Python 스크립트를 실행하려면:

python3 script.py

명령줄

명령줄 대 사용자 정의 스크립트. 슬리더에는 일반적인 버그를 찾는 사전 정의된 탐지기 세트가 함께 제공됩니다. 명령줄에서 슬리더를 호출하면 모든 탐지기가 실행되며, 정적 분석에 대한 자세한 지식이 필요하지 않습니다:

slither project_paths

탐지기 외에도 슬리더는 프린터(printers) (opens in a new tab)도구(tools) (opens in a new tab)를 통한 코드 리뷰 기능을 갖추고 있습니다.

비공개 탐지기 및 GitHub 연동에 액세스하려면 crytic.io (opens in a new tab)를 사용하세요.

정적 분석

슬리더 정적 분석 프레임워크의 기능과 설계는 블로그 게시물(1 (opens in a new tab), 2 (opens in a new tab)) 및 학술 논문 (opens in a new tab)에 설명되어 있습니다.

정적 분석은 다양한 형태로 존재합니다. clang (opens in a new tab)gcc (opens in a new tab)와 같은 컴파일러가 이러한 연구 기술에 의존한다는 것을 알고 계실 것입니다. 하지만 이는 Infer (opens in a new tab), CodeClimate (opens in a new tab), FindBugs (opens in a new tab)와 같은 도구와 Frama-C (opens in a new tab)Polyspace (opens in a new tab)와 같은 정형 기법(formal methods) 기반 도구의 기반이 되기도 합니다.

여기서 정적 분석 기술과 연구를 철저하게 검토하지는 않겠습니다. 대신, 슬리더의 작동 방식을 이해하여 버그를 찾고 코드를 이해하는 데 더 효과적으로 사용할 수 있도록 필요한 내용에 집중할 것입니다.

코드 표현

단일 실행 경로에 대해 추론하는 동적 분석과 달리, 정적 분석은 모든 경로를 한 번에 추론합니다. 이를 위해 다른 코드 표현에 의존합니다. 가장 일반적인 두 가지는 추상 구문 트리(AST)와 제어 흐름 그래프(CFG)입니다.

추상 구문 트리(AST)

AST는 컴파일러가 코드를 파싱할 때마다 사용됩니다. 아마도 정적 분석을 수행할 수 있는 가장 기본적인 구조일 것입니다.

간단히 말해, AST는 구조화된 트리로, 일반적으로 각 리프(leaf)에는 변수나 상수가 포함되고 내부 노드(internal nodes)는 피연산자나 제어 흐름 연산입니다. 다음 코드를 살펴보겠습니다:

function safeAdd(uint a, uint b) pure internal returns(uint){
    if(a + b <= a){
        revert();
    }
    return a + b;
}

해당 AST는 다음과 같습니다:

AST

슬리더는 solc에서 내보낸 AST를 사용합니다.

구축하기는 간단하지만 AST는 중첩된 구조입니다. 때로는 분석하기가 가장 직관적이지 않을 수 있습니다. 예를 들어, a + b <= a 표현식에서 사용된 연산을 식별하려면 먼저 <=를 분석한 다음 +를 분석해야 합니다. 일반적인 접근 방식은 트리를 재귀적으로 탐색하는 이른바 방문자 패턴(visitor pattern)을 사용하는 것입니다. 슬리더는 ExpressionVisitor (opens in a new tab)에 제네릭 방문자를 포함하고 있습니다.

다음 코드는 ExpressionVisitor를 사용하여 표현식에 덧셈이 포함되어 있는지 감지합니다:

제어 흐름 그래프(CFG)

두 번째로 일반적인 코드 표현은 제어 흐름 그래프(CFG)입니다. 이름에서 알 수 있듯이 모든 실행 경로를 노출하는 그래프 기반 표현입니다. 각 노드에는 하나 또는 여러 개의 명령어가 포함됩니다. 그래프의 간선(edges)은 제어 흐름 연산(if/then/else, 루프 등)을 나타냅니다. 이전 예제의 CFG는 다음과 같습니다:

CFG

CFG는 대부분의 분석이 구축되는 기반이 되는 표현입니다.

이 외에도 많은 코드 표현이 존재합니다. 각 표현은 수행하려는 분석에 따라 장단점이 있습니다.

분석

슬리더로 수행할 수 있는 가장 간단한 유형의 분석은 구문 분석(syntactic analyses)입니다.

구문 분석

슬리더는 패턴 매칭과 유사한 접근 방식을 사용하여 코드의 다양한 구성 요소와 그 표현을 탐색하여 불일치 및 결함을 찾을 수 있습니다.

예를 들어 다음 탐지기는 구문 관련 문제를 찾습니다:

의미 분석

구문 분석과 달리 의미 분석(semantic analysis)은 더 깊이 들어가 코드의 "의미"를 분석합니다. 이 제품군에는 몇 가지 광범위한 유형의 분석이 포함됩니다. 더 강력하고 유용한 결과를 도출하지만 작성하기가 더 복잡합니다.

의미 분석은 가장 진보된 취약점 탐지에 사용됩니다.

데이터 종속성 분석

variable_a의 값이 variable_b의 영향을 받는 경로가 있는 경우 변수 variable_avariable_b에 데이터 종속적이라고 합니다.

다음 코드에서 variable_avariable_b에 종속됩니다:

// ...
variable_a = variable_b + 1;

슬리더는 중간 표현(이후 섹션에서 설명) 덕분에 내장된 데이터 종속성 (opens in a new tab) 기능을 제공합니다.

데이터 종속성 사용의 예는 위험한 엄격한 동등성 탐지기(dangerous strict equality detector) (opens in a new tab)에서 찾을 수 있습니다. 여기서 슬리더는 위험한 값에 대한 엄격한 동등성 비교를 찾고(incorrect_strict_equality.py#L86-L87 (opens in a new tab)), 공격자가 컨트랙트를 함정에 빠뜨리는 것을 방지하기 위해 == 대신 >= 또는 <=를 사용해야 함을 사용자에게 알립니다. 무엇보다도 탐지기는 balanceOf(address) 호출의 반환 값을 위험한 것으로 간주하고(incorrect_strict_equality.py#L63-L64 (opens in a new tab)), 데이터 종속성 엔진을 사용하여 그 사용을 추적합니다.

고정점 계산

분석이 CFG를 탐색하고 간선을 따라가다 보면 이미 방문한 노드를 보게 될 가능성이 높습니다. 예를 들어 루프가 아래와 같이 표시되는 경우입니다:

for(uint i; i < range; ++){
    variable_a += 1
}

분석은 언제 중지해야 하는지 알아야 합니다. 여기에는 두 가지 주요 전략이 있습니다. (1) 각 노드에서 유한한 횟수만큼 반복합니다. (2) 이른바 고정점(fixpoint)을 계산합니다. 고정점은 기본적으로 이 노드를 분석해도 의미 있는 정보가 제공되지 않음을 의미합니다.

고정점이 사용된 예는 재진입 탐지기에서 찾을 수 있습니다. 슬리더는 노드를 탐색하고 외부 호출, 스토리지 쓰기 및 읽기를 찾습니다. 고정점에 도달하면(reentrancy.py#L125-L131 (opens in a new tab)) 탐색을 중지하고 결과를 분석하여 다양한 재진입 패턴(reentrancy_benign.py (opens in a new tab), reentrancy_read_before_write.py (opens in a new tab), reentrancy_eth.py (opens in a new tab))을 통해 재진입이 존재하는지 확인합니다.

효율적인 고정점 계산을 사용하여 분석을 작성하려면 분석이 정보를 전파하는 방식에 대한 충분한 이해가 필요합니다.

중간 표현

중간 표현(IR)은 원본 언어보다 정적 분석에 더 적합하도록 만들어진 언어입니다. 슬리더는 Solidity를 자체 IR인 SlithIR (opens in a new tab)로 변환합니다.

기본적인 검사만 작성하려는 경우 SlithIR을 이해할 필요는 없습니다. 하지만 고급 의미 분석을 작성할 계획이라면 유용할 것입니다. SlithIR (opens in a new tab)SSA (opens in a new tab) 프린터는 코드가 어떻게 변환되는지 이해하는 데 도움이 됩니다.

API 기초

슬리더에는 컨트랙트 및 해당 함수의 기본 속성을 탐색할 수 있는 API가 있습니다.

코드베이스를 로드하려면:

from slither import Slither
slither = Slither('/path/to/project')

컨트랙트 및 함수 탐색

Slither 객체에는 다음이 있습니다:

  • contracts (list(Contract): 컨트랙트 목록
  • contracts_derived (list(Contract): 다른 컨트랙트가 상속하지 않는 컨트랙트 목록(컨트랙트의 하위 집합)
  • get_contract_from_name (str): 이름으로 컨트랙트 반환

Contract 객체에는 다음이 있습니다:

  • name (str): 컨트랙트 이름
  • functions (list(Function)): 함수 목록
  • modifiers (list(Modifier)): 함수 목록
  • all_functions_called (list(Function/Modifier)): 컨트랙트에서 도달할 수 있는 모든 내부 함수 목록
  • inheritance (list(Contract)): 상속된 컨트랙트 목록
  • get_function_from_signature (str): 서명으로 함수 반환
  • get_modifier_from_signature (str): 서명으로 제어자(Modifier) 반환
  • get_state_variable_from_name (str): 이름으로 상태 변수(StateVariable) 반환

Function 또는 Modifier 객체에는 다음이 있습니다:

  • name (str): 함수 이름
  • contract (contract): 함수가 선언된 컨트랙트
  • nodes (list(Node)): 함수/제어자의 CFG를 구성하는 노드 목록
  • entry_point (Node): CFG의 진입점
  • variables_read (list(Variable)): 읽은 변수 목록
  • variables_written (list(Variable)): 쓴 변수 목록
  • state_variables_read (list(StateVariable)): 읽은 상태 변수 목록(variables`read의 하위 집합)
  • state_variables_written (list(StateVariable)): 쓴 상태 변수 목록(variables`written의 하위 집합)

페이지 최근 업데이트: 2026년 4월 15일