System Hacking/Fuzzer

fuzz APIS

burrri 2023. 5. 25. 12:51

https://www.fuzzingbook.org/html/APIFuzzer.html

 

Fuzzing APIs - The Fuzzing Book

So far, we have always generated system input, i.e. data that the program as a whole obtains via its input channels. However, we can also generate inputs that go directly into individual functions, gaining flexibility and speed in the process. In this chap

www.fuzzingbook.org

0. Goal

API수준에서 개별 함수를 fuzzing하기 위한 함수를 호출하는 문법을 설정하기

 

1. Fuzzing a Function

urlparse() : url을 받아서 각각의 요소로 분해

urlparse('https://www.fuzzingbook.com/html/APIFuzzer.html')
ParseResult(scheme='https', netloc='www.fuzzingbook.com', path='/html/APIFuzzer.html', params='', query='', fragment='')

 urlparse()를 test하기위해서

for i in range(10):
    url = url_fuzzer.fuzz() 
    print(urlparse(url))
    
    # url_fuzzer객체를 사용하여 url생성후 변수에 할당
    # ParseResult객체 반환

- 이러한 과정을 통해서 Python function을 test

-  scaffolding method

      : test생성 & 실행을 같이 실행해주어야하므로 서로 다른 시간 또는 서로 다른 언어로는 실행불가

      : synthesizing code로 test생성과 실행을 분리 

 

 

2. Synthesizing Code

# <call> 규칙 정의 
URLPARSE_GRAMMAR: Grammar = {  
    "<call>":
        ['urlparse("<url>")']
}

# Import definitions from URL_GRAMMAR
URLPARSE_GRAMMAR.update(URL_GRAMMAR)
URLPARSE_GRAMMAR["<start>"] = ["<call>"] #<start>에 대한 키값을 <call> 로 설정 

assert is_valid_grammar(URLPARSE_GRAMMAR) #유효하지않다면 error

위의 문법은 urlparse(<url>)에서 calls를 생성하는 방식이다

이를 통해서 우리는 calls에 urlparse를 결합하여 fuzzing할 수 있다. 

 

직접 실행해보자

URLPARSE_GRAMMAR
{'<call>': ['urlparse("<url>")'],
 '<start>': ['<call>'],
 '<url>': ['<scheme>://<authority><path><query>'],
 '<scheme>': ['http', 'https', 'ftp', 'ftps'],
 '<authority>': ['<host>',
  '<host>:<port>',
  '<userinfo>@<host>',
  '<userinfo>@<host>:<port>'],
 '<host>': ['cispa.saarland', 'www.google.com', 'fuzzingbook.com'],
 '<port>': ['80', '8080', '<nat>'],
 '<nat>': ['<digit>', '<digit><digit>'],
 '<digit>': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<userinfo>': ['user:password'],
 '<path>': ['', '/', '/<id>'],
 '<id>': ['abc', 'def', 'x<digit><digit>'],
 '<query>': ['', '?<params>'],
 '<params>': ['<param>', '<param>&<params>'],
 '<param>': ['<id>=<id>', '<id>=<nat>']}

여러 데이터의 구성요소가 한번에 나오는 것을 볼 수 있다.

 

# Call function_name(arg[0], arg[1], ...) as a string
def do_call(call_string):
    print(call_string)
    result = eval(call_string)
    print("\t= " + repr(result))
    return result
call = urlparse_fuzzer.fuzz()
do_call(call)

결과는 다음과 같다.

urlparse("http://www.google.com?abc=def")
	= ParseResult(scheme='http', netloc='www.google.com', path='', params='', query='abc=def', fragment='')
ParseResult(scheme='http', netloc='www.google.com', path='', params='', query='abc=def', fragment='')

: synthesizing code 는 일부의 fatal errors나 exceptions만 유효한 지 확인한다.

 

 

3. Synthesizing Oracles

: 전체적으로 test하기 위해서는 결과가 유효한지 확인하는 oracle을 설정해야한다. 

:  URL의 특정부분이 결과에 다시나타나는 지 확인해야한다. 

 ( 예: scheme이 http라면 ParseResult는 http scheme을 포함해야한다)

 

유효성을 통과하려면 다음의 예시에서 urlparse()속 geturl()의 결과는 url을 반환해야한다. 

from GeneratorGrammarFuzzer import GeneratorGrammarFuzzer, ProbabilisticGeneratorGrammarFuzzer
URLPARSE_ORACLE_GRAMMAR: Grammar = extend_grammar(URLPARSE_GRAMMAR,
{
     "<call>": [("assert urlparse('<url>').geturl() == '<url>'",
                 opts(post=lambda url_1, url_2: [None, url_1]))] #url parsing결과 반환
})
# call 규칙 추가
# 주어진 URL을 파싱한 결과의 geturl() 메서드와 원래의 URL이 동일한지를 확인
urlparse_oracle_fuzzer = GeneratorGrammarFuzzer(URLPARSE_ORACLE_GRAMMAR)
test = urlparse_oracle_fuzzer.fuzz()
print(test)

# GeneratorGrammarFuzzer의 객체인 urlparse_oracle_fuzzer를 생성 
# 위의 객체에서 fuzz()를 통해 test케이스를 생성하여 test변수에 할당
assert urlparse('https://user:password@cispa.saarland/abc?abc=abc').geturl() 
== 'https://user:password@cispa.saarland/abc?abc=abc'

# 파싱한 결과(geturl())의 결과가 원래의 url과 동일한지 비교
# 동일하지않다면 error를 발생
exec(test) #execute

위와 같은 문법을 통해  arbitrary programming language와 APIS에 대해서 테스트를 생성할 수 있다.

 

 

4. Synthesizing Data

:  함수에는 다양한  basic data types가 존재하는데 기본 데이터유형의 인자를 정확하게 나타내기 위해 구체적인 문법을 살펴보자

 

- Integers 정수

from Grammars import convert_ebnf_grammar, crange
from ProbabilisticGrammarFuzzer import ProbabilisticGrammarFuzzer
INT_EBNF_GRAMMAR: Grammar = {
    "<start>": ["<int>"],
    "<int>": ["<_int>"],
    "<_int>": ["(-)?<leaddigit><digit>*", "0"],
    "<leaddigit>": crange('1', '9'),
    "<digit>": crange('0', '9')
}

assert is_valid_grammar(INT_EBNF_GRAMMAR)

# <start>는 <int>규칙을, <int>는 <_int>를 포함
# leaddigit : 1~9, digit : 0~9
# <_int>의 첫표현은 음수부호포함, leaddigit,digit로 이루어진 정수이며
#두번째 표현은 0을 표현
INT_GRAMMAR = convert_ebnf_grammar(INT_EBNF_GRAMMAR)
INT_GRAMMAR #문법 변환후 파싱
# EBNF -> BNF문법 변환 결과 (도구와 라이브러리의 원활한 통합을 위해)
{'<start>': ['<int>'],
 '<int>': ['<_int>'],
 '<_int>': ['<symbol-1><leaddigit><digit-1>', '0'],
 '<leaddigit>': ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<digit>': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<symbol>': ['-'],
 '<symbol-1>': ['', '<symbol>'],
 '<digit-1>': ['', '<digit><digit-1>']}
int_fuzzer = GrammarFuzzer(INT_GRAMMAR) 
print([int_fuzzer.fuzz() for i in range(10)])

#int_fuzzer객체 생성
#10개의 정수 생성후 리스트로 저장, 출력
['699', '-44', '321', '-7', '-6', '67', '0', '0', '57', '0']

 

 

- Floats 실수

: 정수와 유사

FLOAT_EBNF_GRAMMAR: Grammar = {
    "<start>": ["<float>"],
    "<float>": [("<_float>", opts(prob=0.9)), "inf", "NaN"], #부동소수점 숫자의 구조정의
    "<_float>": ["<int>(.<digit>+)?<exp>?"],  #부동소수점 숫자의 주요 구조 (확률 0.9)
    "<exp>": ["e<int>"] # 지수부분 정의
}
FLOAT_EBNF_GRAMMAR.update(INT_EBNF_GRAMMAR)
FLOAT_EBNF_GRAMMAR["<start>"] = ["<float>"]

assert is_valid_grammar(FLOAT_EBNF_GRAMMAR)
FLOAT_GRAMMAR = convert_ebnf_grammar(FLOAT_EBNF_GRAMMAR)
FLOAT_GRAMMAR
{'<start>': ['<float>'],
 '<float>': [('<_float>', {'prob': 0.9}), 'inf', 'NaN'],
 '<_float>': ['<int><symbol-2><exp-1>'],
 '<exp>': ['e<int>'],
 '<int>': ['<_int>'],
 '<_int>': ['<symbol-1-1><leaddigit><digit-1>', '0'],
 '<leaddigit>': ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<digit>': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<symbol>': ['.<digit-2>'],
 '<symbol-1>': ['-'],
 '<symbol-2>': ['', '<symbol>'],
 '<exp-1>': ['', '<exp>'],
 '<symbol-1-1>': ['', '<symbol-1>'],
 '<digit-1>': ['', '<digit><digit-1>'],
 '<digit-2>': ['<digit>', '<digit><digit-2>']}
float_fuzzer = ProbabilisticGrammarFuzzer(FLOAT_GRAMMAR)
print([float_fuzzer.fuzz() for i in range(10)])
#위에서 정의한 규칙에 따라 10개의 부동소수점 숫자를 생성
['0', '-4e0', '-3.3', '0.55e0', '0e2', '0.2', '-48.6e0', '0.216', '-4.844', '-6.100']
 
 

- Strings

: EBNF표기법을 사용하여 ASCII 문자열을 생성하는 문법과 과정을 보자 

ASCII_STRING_EBNF_GRAMMAR: Grammar = {
    "<start>": ["<ascii-string>"],
    "<ascii-string>": ['"<ascii-chars>"'],
    "<ascii-chars>": [
        ("", opts(prob=0.05)),
        "<ascii-chars><ascii-char>"
    ],
    "<ascii-char>": crange(" ", "!") + [r'\"'] + crange("#", "~")
}

assert is_valid_grammar(ASCII_STRING_EBNF_GRAMMAR)

# <ascii-chars>: 빈문자열 또는 <ascii-chars><ascii-char>의 조합으로 구성
# <ascii-char> : 공백부터 !까지의 문자, ", #부터 ~까지의 문자로 이뤄진 문자열 생성
#EBNF -> BNF변환
ASCII_STRING_GRAMMAR = convert_ebnf_grammar(ASCII_STRING_EBNF_GRAMMAR)
string_fuzzer = ProbabilisticGrammarFuzzer(ASCII_STRING_GRAMMAR)
print([string_fuzzer.fuzz() for i in range(10)])
['"BgY)"', '"j[-64Big65wso(f:wg|}w&*D9JthLX}0@PT^]mr[`69Cq8H713ITYx<#jpml)\\""', '"{);XWZJ@d`\'[h#F{1)C9M?%C`="', '"Y"', '"C4gh`?uzJzD~$\\\\"=|j)jj=SrBLIJ@0IbYiwIvNf5#pT4QUR}[g,35?Wg4i?3TdIsR0|eq3r;ZKuyI\'<\\"[p/x$<$B!\\"_"', '"J0HG33+E(p8JQtKW.;G7 ^?."', '"7r^B:Jf*J.@sqfED|M)3,eJ&OD"', '"c3Hcx^&*~3\\"Jvac}cX"', '"\'IHBQ:N+U:w(OAFn0pHLzX"', '"x4agH>H-2{Q|\\kpYF"']

 

 

5. Synthesizing Composite Data

- Lists

LIST_EBNF_GRAMMAR: Grammar = {
    "<start>": ["<list>"],
    "<list>": [
        ("[]", opts(prob=0.05)),
        "[<list-objects>]"
    ],
    "<list-objects>": [
        ("<list-object>", opts(prob=0.2)),
        "<list-object>, <list-objects>"
    ],
    "<list-object>": ["0"], #리스트 객체를 나타내는 규칙, 현재 숫자 0을 가리킴
}

assert is_valid_grammar(LIST_EBNF_GRAMMAR)
LIST_GRAMMAR = convert_ebnf_grammar(LIST_EBNF_GRAMMAR)
#객체문법 & list객체의 symbol를 인자로 하는 객체list문법생성하는 함수 
def list_grammar(object_grammar, list_object_symbol=None):
    obj_list_grammar = extend_grammar(LIST_GRAMMAR)
    if list_object_symbol is None: #none인 경우 
        list_object_symbol = object_grammar[START_SYMBOL][0] 
        # <start>의 첫번째 symbol을 을 list symbol로 사용 (디폴트값) 

    obj_list_grammar.update(object_grammar)	#객체문법의 규칙이 추가 
    obj_list_grammar[START_SYMBOL] = ["<list>"] #시작symbol을  <list>로 
    obj_list_grammar["<list-object>"] = [list_object_symbol] 
    #<list-object>규칙을 list_object_symbol로 설정 > 실제 객체를 나타내는 규칙!

    assert is_valid_grammar(obj_list_grammar)

    return obj_list_grammar #최종적으로 생성된 객체list문법 반환

 

int의 경우일 때를 보자  (float, string 다 가능하다) 

int_list_fuzzer = ProbabilisticGrammarFuzzer(list_grammar(INT_GRAMMAR))
[int_list_fuzzer.fuzz() for i in range(10)]
# INT_GRAMMER를 기반으로 list-grammer 함수를 사용하여 객체list문법생성
# ProbabilisticGrammarFuzzer를 통해 정수로 구성된 리스트를 확률적으로 생성하는 int_list_fuzzer생성
['[0, -4, 23, 0, 0, 9, 0, -6067681]',
 '[-1, -1, 0, -7]',
 '[-5, 0]',
 '[1, 0, -628088, -6, -811, 0, 99, 0]',
 '[-35, -10, 0, 67]',
 '[-3, 0, -2, 0, 0]',
 '[0, -267, -78, -733, 0, 0, 0, 0]',
 '[0, -6, 71, -9]',
 '[-72, 76, 0, 2]',
 '[0, 9, 0, 0, -572, 29, 8, 8, 0]']

위와 같이 10개의 정수 list가 반환된 것을 볼 수 있다. 

 

 

 

 

'System Hacking > Fuzzer' 카테고리의 다른 글

Testing Graphical User Interfaces  (0) 2023.05.25