Fuzzer 1주차

전수경·2023년 4월 6일
0

fuzzer

목록 보기
1/1

01. Introduction to Software Testing

+수동 테스트 코드

def my_sqrt(x):
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

1.Automating Test Execution(자동화 테스트 실행)

+수동 테스트의 단점: 수동으로 매우 제한된 수의 실행과 그 결과 만 확인하며, 프로그램을 변경 한 후에는 테스트 프로세스를 반복해야 한다.=>테스트 자동화가 유용함.

result = my_sqrt(4)
expected_result = 2.0
if result == expected_result:
    print("Test passed")
else:
    print("Test failed")입력하세요

Test passed

위 코드의 문제점

  1. 단일 테스트에 5줄의 코드가 필요하다.
  2. 반올림 오류를 고려하지 않는다.
  3. 단일 입력(및 단일 결과)만 확인한다.

Solution

  1. Python의 assert 사용
  • floating-point values 차이의 오류가 분명히 있음.
    => ε(Epsilon) 이용해서 특정 임계값 미만으로 유지되도록 하는 방법 이용
def assertEquals(x, y, epsilon=1e-8):
    assert abs(x - y) < epsilon
    
	assertEquals(my_sqrt(4), 2)
	assertEquals(my_sqrt(9), 3)
	assertEquals(my_sqrt(100), 10) 

+√xX√x=x 역시 이상 없으며 수천 개의 값을 빠른 속도로 쉽게 테스트 가능함.

Run-time Verification

:런타임 검사의 중요한 제한 사항은 검사할 결과가 있는 경우에만 정확성을 보장한다. 즉, 항상 결과가 있을 것이라고 보장하지는 않는다.

System input VS Function input

def sqrt_program(arg: str) -> None:
    x = int(arg)
    print('The root of', x, 'is', my_sqrt(x))
    
sqrt_program("4")

The root of 4 is 2.0.

아무 문제 없어 보이지만 sqrt_program(-1) 호출 시에는? 무한 루프에 빠짐

from ExpectError import ExpectTimeout
with ExpectTimeout(1):
    sqrt_program("-1")

=>위와 같은 구조를 이용해서 1초 후 실행 중단시킴

:오류 메시지

Solution

def sqrt_program(arg: str) -> None:
    x = int(arg)
    if x < 0:
        print("Illegal Input")
    else:
        print('The root of', x, 'is', my_sqrt(x))
sqrt_program("-1")

Illegal Input

그렇다면 문자열이라면?

from ExpectError import ExpectError
with ExpectError():
    sqrt_program("xyzzy")


:오류 메시지

Solution

def sqrt_program(arg: str) -> None:
    try:
        x = float(arg)
    except ValueError:
        print("Illegal Input")
    else:
        if x < 0:
            print("Illegal Number")
        else:
            print('The root of', x, 'is', my_sqrt(x))

프로그램은 모든 종류의 입력을 정상적으로 처리 가능하게 해야함.

한계

with ExpectError():
    root = my_sqrt(0)


:오류 메시지

02.Fuzzing: Breaking Things with Random Inputs

+https://www.fuzzingbook.org/html/Importing.html
(fuzzer 사용법 참고)

1. Fuzzers

: Fuzzer는 RandomFuzzer와 함께 fuzzers의 기본 클래스이다. fuzz()는 Fuzzer 객체의 메소드이며 도출된 문자열을 반환해준다. 퍼징의 본질은 "Create random inputs, and see if they break things. Just let it run long enough, and you'll see."이다.
ex)

random_fuzzer = RandomFuzzer()
random_fuzzer.fuzz()
  • RandomFuzzer() 생성자는 여러 키워드 인수를 지정할 수 있다.
print(RandomFuzzer.__init__.__doc__)
Produce strings of `min_length` to `max_length` characters
           in the range [`char_start`, `char_start` + `char_range`)

2.Runners

: Fuzzer는 Runner와 쌍을 이룰 수 있다. Runners는 클래스의 상태와 결과(PASS, FAIL, or UNRESOLVED)를 출력해준다.

3. A Simple Fuzzer

: 무작위 문자열 생성 뒤 버퍼 문자 변수에 더해준 뒤 string으로 반환하는 방식을 이용해 fuzz generator을 만든다.
-random.randrange(start, end) – return a random number 난수 반환 [start, end )

-range(start, end) – create an iterator (which can be used as a list) with integers in the range [start, end )

-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

import random

def fuzzer(max_length: int = 100, char_start: int = 32, char_range: int = 32) -> str:
    """A string of up to `max_length` characters
       in the range [`char_start`, `char_start` + `char_range`)"""
    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
fuzzer()

=>랜덤 문자열 반환

+ord(c)를 통해 아스키코드 값 이용해서 범위 생성 방법도 있음.

4. Fuzzing External Programs

: fuzzed 된 입력이 있는 외부 프로그램 호출

1. fuzzed test data가 있는 input 파일 생성

import os
import tempfile

basename = "input.txt"
tempdir = tempfile.mkdtemp()
FILE = os.path.join(tempdir, basename)
print(FILE) #파일 생성

data = fuzzer()
with open(FILE, "w") as f:
    f.write(data)
#파일에 작성   
contents = open(FILE).read()
print(contents)
assert(contents == data)
#파일에 작성 후 확인

2. 해당 input 파일을 선택한 프로그램에 공급(외부 프로그램 불러오기)

import os
import subprocess
program = "bc"
with open(FILE, "w") as f:
    f.write("2 + 2\n")
result = subprocess.run([program, FILE],
                        stdin=subprocess.DEVNULL,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        universal_newlines=True)


=> 결과 확인, 산술 연산의 결과

=> 상태 확인(0이면 정산 반환값)

=> 오류 메시지

5. Bugs Fuzzers Find

1. Buffer Overflows

: 입력의 최대 길이(미리 정해진) 등을 넘었을 시에 버퍼오버플로우.

def crash_if_too_long(s):
    buffer = "Thursday"
    if len(s) > len(buffer):
        raise ValueError
from ExpectError import ExpectError

trials = 100
with ExpectError():
    for i in range(trials):
        s = fuzzer()
        crash_if_too_long(s)


: 오류 메시지

2. Missing Error Checks

:여러 프로그래밍 언어에서는 예외적인 상황에서 특수한 오류 코드를 반환한다. ex) C언어의 getchar()=>EOF(end of file) 오류

def hang_if_no_space(s):
    i = 0
    while True:
        if i < len(s):
            if s[i] == ' ':
                break
        i += 1
from ExpectError import ExpectTimeout

trials = 100
with ExpectTimeout(2):
    for i in range(trials):
        s = fuzzer()
        hang_if_no_space(s)

=>시간 초과 메커니즘을 사용하여 잠시 후 이 기능을 중단한다. 코드 실행 중단 및 오류 메시지 출력

3. Rogue Numbers

: 크기가 프로그램 메모리 넘어갈 만큼 크거나 문자열의 수보다 작을 때 등등 문제 발생. (generate uncommon values in the input)

def collapse_if_too_large(s):
    if int(s) > 1000:
        raise ValueError
        
long_number = fuzzer(100, ord('0'), 10)
print(long_number)

with ExpectError():
    collapse_if_too_large(long_number)


: 에러 메시지

6. Catching Errors

1. Checking Memory Accesses

C 언어를 통한 간단한 예시

#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv) {
    /* Create an array with 100 bytes, initialized with 42 */
    char *buf = malloc(100);
    memset(buf, 42, 100);

    /* Read the N-th element, with N being the first command-line argument */
    int index = atoi(argv[1]);
    char val = buf[index];

    /* Clean up memory so we don't leak */
    free(buf);
    return val;
}
!./program 110

buf[110]에 접근하려면 AdressSanitizer의 범위를 넘어간 에러 발생

2. Information Leaks

: 정보 노출은 유효한 메모리에서도 충분히 일어날 수 있음.

secrets = ("<space for reply>" + fuzzer(100) +
           "<secret-certificate>" + fuzzer(100) +
           "<secret-key>" + fuzzer(100) + "<other-secrets>")

=>실제 데이터와 임의의 데이터로 만들어진 프로그램 메모리

uninitialized_memory_marker = "deadbeef"
while len(secrets) < 2048:
    secrets += uninitialized_memory_marker
def heartbeat(reply: str, length: int, memory: str) -> str:
    # Store reply in memory
    memory = reply + memory[len(reply):]

    # Send back heartbeat
    s = ""
    for i in range(length):
        s += memory[i]
    return s
heartbeat("hat", 500, memory=secrets)

이러한 문제 감지 코드

from ExpectError import ExpectError

with ExpectError():
    for i in range(10):
        s = heartbeat(fuzzer(), random.randint(1, 500), memory=secrets)
        assert not s.find(uninitialized_memory_marker)
        assert not s.find("secret")


: 오류 메시지

Exercises

1. 해당 실패 조건에 맞는 파이썬 함수 f(s) 만들기

import string
def no_backslash_d(inp):
    pattern = "\\D"
    index = inp.find(pattern)
    if index < 0 or index + len(pattern) >= len(inp):
        return True
    c = inp[index + len(pattern)]
    assert c in string.printable
with ExpectError():
    no_backslash_d("\\D\0")
def no_8bit(inp):
    for i in range(len(inp) - 1):
        assert ord(inp[i]) <= 127 or inp[i + 1] != '\n'
    return True
with ExpectError():
    no_8bit("ä\n")
def no_dot(inp):
    assert inp != ".\n"
    return True

2.

real_troff_runner = BinaryProgramRunner("troff")
for i in range(100):
    result, outcome = random_fuzzer.run(real_troff_runner)
    if outcome == Runner.FAIL:
        print(result)
profile
Cyber Security

0개의 댓글