관리 메뉴

개발자비행일지

The Fuzzing Book 공부1 본문

▶ 퍼징

The Fuzzing Book 공부1

Cyber0946 2020. 2. 19. 13:49

Fuzzing: Breaking Things with Random Inputs

https://www.fuzzingbook.org/

 

The Fuzzing Book

Welcome to "The Fuzzing Book"! Software has bugs, and catching bugs can involve lots of effort. This book addresses this problem by automating software testing, specifically by generating tests automatically. Recent years have seen the development of novel

www.fuzzingbook.org

Fuziing Book에 대해서 본격적으로 학습하기 앞서서 먼저 여기서 제공하는 모듈들을 설치하는 것이 선행되어야 한다. 

가장 먼저 cmd 창을 열고 pip install fuzzingbook 을 통해 설치 해준다. 

 

#해당 사이트에서 제공하는 자료들은 코드로도 다운 받을 수 있으며, 주피터 노트북으로도 사용 가능하다. 

#본인은 데스크탑 환경에서 직접 해보기 위해서 소스코드 파일을 다운로드 받아서 파이썬이 설치되어 있는 lib폴더에 복사한 뒤 사용하였다. 

개요

간단한 테스트 생성 기술을 알아보자.

쉽게 생각하면 퍼징은 임의의 텍스트를 생성해서 프로그램에 입력 값으로 주고, 실패를 찾아내는 것이다.

이 챕터에서 제공하는 코드를 사용하기 위해서 다음과 같이 작성해 준다. 

from fuzzingbook.Fuzzer import <사용할 모듈>예) RadomFuzzer

RandomFuzzer () 생성자는 여러 키워드 인수를 지정할 수 있습니다.

설치가 되면 이제 import 문을 통해서 해당 라이브러리를 가져 올 수 있다.

아래와 같은 예제를 통해 기본적인 fuzzer를 사용해 보자.

Fuzzer

Fuzzer은 fuzzer들의 기본 class이먀, RandomFuzzer()는인스탄수를 생성해준다. 여기의 fuzz() 함수는 fuzzer 객체를 사용해 임의의 문자열을 생성해주는 함수이다. 

fuzz() 함수의 return 값을 통해 임의의 문자열이 생성됨을 알 수 있으며, 이는 퍼징 테스팅의 입력값으로 사용된다. 

RadomFuzzer()는 여러 키워드 인수를 지정 가능하다. 

다음의 명령을 통해서 매개변수를 확인 가능하다. 

이런식으로 최소길이, 최대길이, 시작하는 문자, 문자열의 종류를 설정하여 사용 가능하다. 

Runners

Fuzzer는 Runner와 한쌍이다, Runner를 통해서 생성된 문자열을 입력으로 사용가능하다.

그리고 Runner는 클래스별로 상태와 결괄릅 나호나해 준다. Print Runner는 주어진 입력과 그리고 PASS결과가 어떻게 되는지에 대해 출력해준다. 

A Testing Assignment

퍼징은 "1988 년 가을 폭풍의 치는 밤에 탄생 했다.  [Takanen et al, 2008.]. 매디슨의 위스콘신에 있는 그의 아파트에 앉아있는 Barton Miller 교수는 1200 보오 전화선을 통해 대학 컴퓨터에 연결되었는데, 뇌우로 인해 회선에 낀 소음이 한쪽 끝에 연결 되어 있는 UNUX에 잘못된 명령을 전달하게 되었고, 이로 인해 시스템 CRASH가 발생하였다. 이를 계기로 그는 프로그램의 robust를 테스트 하기 위한 프로그램을 개발하기 시작했고 이것이ㅣ 첫번째 fuzzer가 되었다. 첫번째 fuzzer의 목표난 아래의 두가지 였다. 

과제 1. 랜덤 문자열을 생성하라, 2. 이를 통해서 UNIX 시설들을 공격해서 파괴하라

아래 논문 참고

This assignment captures the essence of fuzzing: Create random inputs, and see if they break things. Just let it run long enough and you'll see.

A Simple Fuzzer

간단한 fuzzer를 만들어 보자. 파이썬을 사용한다. 

  • random.randrange(start, end) – return a random number [[ start, end ))
  • range(start, end) – create a list with integers in the range [[ start, end )). Typically used in iterations.
  • for elem in list: body – execute body in a loop with elem taking each value from list.
  • for i in range(start, end): body – execute body in a loop with i from start to end  1.
  • chr(n) – return a character with ASCII code n

1. 기본 아이디어는 먼저 문자열의 길이를 random.randrange를 통해서 max_length를 통해 결정한 문자열의 길이 제한 내에서 선택해 온다.

2. out이라는 문자열 변수를 통해서 선택되는 임의의 문자를 for문을 통해서 stirng_length만큼 반복해서 한개씩 더해준다. 

3. 더해줄 때, 정수 값으로 가져 왔기 때문에 chr() 함수를 통해서 ASCII 코드로 변경해 준다. 

4. out에 저장된 문자열은 반환해 준다. 

해당 함수를  Firstfuzzer,.py로 저장하고 실행한 다음 

fuzzer() 를 통해 기본 매개변수로 실행한 결과는 다믕과 같다. 

Bart Miller는 fuzz라는 이름을 작위적이고, 구조화 되지 않은 데이터를 명명하기 위해 결정했다. 

자, 만약 특정한 형식을 가지는 입력을 처리하는 프로그램이 이러한 퍼징문자열을 만났을 때, 아무 문제 없이 동작한다고 할 수 있는가?

앞서 해본 간단한 예제가 흥미롭다면 아래의 내용들을 통해서 좀 더 다양한 퍼징을 진행해 보자. 예를 들어 우리는 가지고 있는 Firstfuzzer.py와 파이썬에서 재공하는 내장형 함수중 ord() 함수를 통해서 lower case를 반환하는 무작위 입력값을 생성할 수 있다. 

Fuzzing External Programs

자 이제 우리가 실제로 다른 프로그램에다가 fuzzer()로 생상헌 입력을 넣으면 어떻게 되는지 알아보자. 이를 위해 두 단계로 진행하는데 먼저 퍼징 된 테스트 데이터로 입력 파일을 만들고 그리고 그 파일을 타겟 프로그램에 입력한다. 

Creating Input Files

입력 파일을 생성한다. 

우리는 이제 쓰기 가능한 파일을 열 수 있다. 파이썬에서 제공하는 open()함수ㄹ를 통해서 우리는 파일을 열고 우리가 원하는 데이터를 채워넣을 수 있다. 

data = fuzzer()

with open(FILE, "w") as f:

    f.wirte(data)

우리는 파일을 열고 fuzzer()에서 생성되는 값을 채워 넣을 수 있다.  

종합하면 다음과 같다. 작성한 Secondfuzzer.py는 아래와 같다.

Invoking External Programs

이제 우리가 만든 입력 파일을 실제로 다른 프로그램에 넣어 보자. 재미를 위해서 우리는 계산기 프로그램을 타겟으로 해서 퍼징을 진행해 보도록 한다. 

실행결과는 다믕과 같다. 

타겟 프로그램을 notepad를 대상으로 했을 때의 결과이다. 

 

자 이제 이를 종합해서 makeinput() 명령을 통해서 program을 대상으로 해당 명령을 하는 프로그램을 작성해 보면 아래와 같다. 

import os
import subprocess
import random
import os
import tempfile

#파이썬을 사용한 fuzzer함수 정의 매개변수는 최대 길이, 시작하는 문자, 문자 선택 범위
def fuzzer(max_length = 100, char_start =32, char_range=32):
        string_length = random.randrange(0, max_length +1)
        out = ""
        for i in range(0, string_length):
                out += chr(random.randrange(char_start, char_start + char_range))
        return out
    
def makeinput(max_length = 100, char_start =32, char_range=32):
        basename = "input.txt"
        tempdir = tempfile.mkdtemp()
        FILE = os.path.join(tempdir, basename)
        print("입력을 위한 파일의 경로 :%s",FILE)
        program = "notepad"
        data = fuzzer(max_length, char_start, char_range)
        with open(FILE, "w") as f:
                f.write(data)
        print("생성된 입력값을 확인합니다.\n")
        contents = open(FILE).read()
        print(contents)
        assert(contents == data)
        print("프로그램을 대상으로 공격을 수행합니다. 실행합니다.")
        result = subprocess.run([program, FILE],stdin=subprocess.DEVNULL,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) 
        result.stdout
        print("결과를 출력합니다.:%s",result)

Long-Running Fuzzing

자 이제 실제 테스팅 처럼 수 많은 입력을 공격으로 시도해 보자

i
import os
import subprocess
import random
import os
import tempfile

#파이썬을 사용한 fuzzer함수 정의 매개변수는 최대 길이, 시작하는 문자, 문자 선택 범위
def fuzzer(max_length = 100, char_start =32, char_range=32):
        string_length = random.randrange(0, max_length +1)
        out = ""
        for i in range(0, string_length):
                out += chr(random.randrange(char_start, char_start + char_range))
        return out
    
def makeinput(max_length = 100, char_start =32, char_range=32):
        trial = 100
        runs = []
        basename = "input.txt"
        tempdir = tempfile.mkdtemp()
        FILE = os.path.join(tempdir, basename)
        print("입력을 위한 파일의 경로 :%s",FILE)
        program = "calc"
        for i in range(trial):
            data = fuzzer(max_length, char_start, char_range)
            with open(FILE, "w") as f:
                f.write(data)
                print("프로그램을 대상으로 %d번째 공격을 수행합니다.\n"%i)
            result = subprocess.run([program, FILE],stdin=subprocess.DEVNULL,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True)
            runs.append((data, result))
        print(runs)

해당 프로그램을 계산기를 대상으로 실행한 결과이다. 

이 텍스트들은 시도한 결과에 대한 로그라고 할 수 있다.

여기에 다음과 같은 명령을 통해서 , stderr가 발생하지 않은 경우의 수를 카운트 할 수 있다. 

sum(1 for (data, result) in runs if result.stderr == "")

 

'▶ 퍼징' 카테고리의 다른 글

The Fuzzing Book 공부 3  (0) 2020.02.24
The Fuzzing Book 공부2  (0) 2020.02.21