Fuzzing
Fuzzing은 자동화된 소프트웨어 테스트 기법으로, 컴퓨터 프로그램에 무작위 데이터를 입력하여 소프트웨어에 Crash 혹은 메무리 누수등 개발자가 생각지 못한 케이스에서 벌어지는 상황을 테스트해 소프트웨어의 견고성을 높이는데 사용된다.
대상 소프트웨어의 input을 자동으로 생성하여 Testing하고 버그를 유발하는 데이터를 찾는 것이 목표인 행위입니다.
Fuzzing을 자동화시켜 프로그램을 테스트할 경우 효율적이고 빠르게 대상 프로그램에 대해서 버그를 찾을 수 있으며, 소스 코드를 가지고 있지 않더라도 버그를 찾을 수 있으므로 요즘에는 많이 프로그램의 버그를 찾는데 있어서 빠질 수 없는 방법론 입니다.
Fuzzing의 상황은 크게 세 가지로 분류할 수 있습니다.
White-Box Testing
소스 코드를 가지고 있는 상태에서 할 수 있는 테스팅입니다.
소스 코드를 가지고 있다는 특징 덕분에 아래의 Testing 기술을 포함하고 있습니다.
1.
Control-Flow Test
2.
Data Flow Test
3.
Branch Test
4.
Prime Path Test
5.
Path Test
Gray-Box Testing
내부에서 사용하는 데이터 구조체 혹은 알고리즘에 대한 문서를 통해 내부 프로그램의 구조를 부분적으로 알고 Testing 하는 테스팅입니다.
아래의 Testing 기술을 포함하고 있습니다.
1.
Matrix Test
2.
Regression Test
3.
Pattern Test
4.
Orthogonal Array Test
Black-Box Testing
소스 코드를 가지고 있지 않는 상태에서 할 수 있는 테스팅입니다.
소스 코드를 가지고 있지 않기 때문에 Binaray만을 이용하여 Testing 합니다.
아래의 Testing 기술을 포함하고 있습니다.
1.
Dumb Based Test
2.
Desision Table Test
3.
All-pairs Test
4.
Syntax Test
5.
Use Case Test
6.
User Story Test
Fuzzer
Fuzzer란 Fuzzing을 자동으로 실행하고 테스트한 뒤 이상행위가 발생할 경우 해당 테스트 케이스를 저장하고 버그를 분류하는 자동화 Fuzzing 도구 입니다.
Code Coverage
Code Coverage는 Fuzzing을 진행할 때 테스트가 얼마나 충분한가를 나타내는 지표중 하나입니다.
소프트웨어의 코드를 얼마나 커버했느냐를 나타내며 이는 곧 테스트 시 코드 자체 얼마나 실행이 되었는지 나타냅니다.
코드의 구조를 이루는 것은 크게 구문(Statement), 조건(Condition), 결정(Desision)입니다. 이러한 구조를 얼마나 커버했느냐에 따라서 측정 기준이 달라집니다.
1.
Statement/Line
코드가 한번 이상 실행되면 충족됩니다.
아래의 코드를 예제로 설명하겠습니다.
void test(int x)
{
printf("Test Start!\n"); //case 1
if (x > 0) //case2
printf("X > 0\n"); //case 3
printf("Test End\n"); //case 4
}
C
복사
위 코드에서 test(0)를 입력했을 경우 case1, case2, case4은 실행되지만 case3의 경우 실행되지 않습니다.
이러한 경우 4 case에서 3개만 실행되었으므로 입니다.
2.
Condition
각 분기문들에 대한 Coverage입니다.
아래의 코드를 예제로 설명하겠습니다.
void test(int x, int y)
{
printf("Test Start!\n");
if (x > 0 && y < 0)
printf("X > 0\n");
printf("Test End\n");
}
C
복사
위 코드의 조건식에서 Condition Coverage를 모두 만족하는 입력 값은 test(1, 1)와 test(-1, -1)이 존재합니다. x > 0의 조건식에서 true/false를 모두 만족하며 y < 0의 조건식에서도 true/false를 모두 만족합니다.
하지만 조건식은 무조건 false를 반환합니다.
3.
Desision/Branch
모든 조건식이 true/false을 가지면 충족됩니다.
아래의 코드를 예제로 설명하겠습니다.
void test(int x, int y)
{
printf("Test Start!\n");
if ((x > 0) && (y > 0))
printf("X > 0\n");
printf("Test End\n");
}
C
복사
위 코드의 조건식에서 true/false 모두 가질 수 있는 입력 값은 test(1, 1)와 test(0, 1)이 있습니다. 첫번째는 x > 0과 y > 0을 모두 만족해 true를 두번째는 x < 0을 만족해 false를 반환합니다. 모든 조건식을 만족함으로 Desision 혹은 Branch Coverage를 만족합니다.
이러한 3가지 Code Coverage 중 Statement Coverage가 가장 많이 사용됩니다.
이러한 Coverage Tool로는 DT10, LDRA, VectorCAST, CoeScroller Tester, QualityScrool Cover이 존재합니다.
Fuzzer Architecture
일반적으로 Fuzzer는 크게 3가지 부분으로 분류됩니다.
1. TestCase Generator
Target Program에 입력할 데이터를 생성합니다.
입력할 데이터를 생성하는 방식에 따라 2가지로 분류됩니다.
1.
Smart Fuzzer
프로그램의 입력 데이터 구조를 파악하여 해당 구조에 맞게 Mutation을 진행합니다.
2.
Dumb Fuzzer
랜덤한 데이터를 생성하여 Mutation을 진행합니다.
2. Logger
Fuzzing을 돌리는 중 버그 혹은 이상행위 분석에 필요한 정보를 저장합니다.
Crash와 Test Case를 저장하며, Code Coverage-Based Fuzzer의 경우 새롭게 발견한 Code Coverage도 저장합니다.
이러한 Log를 확인하여 Crash에 대한 분석을 진행하게 됩니다.
3. Worker
Gernerator에서 생성한 입력 데이터를 받아 실행하고 이상행위를 탐지합니다.
TestCase Generator에서 생성한 Test Case를 실행하며, 이상행위를 탐지합니다.
Fuzzing Case
Dumb Fuzzing
랜덤한 데이터를 생성하여 대상 소프트웨어에 전달하는 방식으로 프로그램에 대한 이해도 없이도 진행할 수 있습니다.
Dumb Fuzzing의 단점
대상 소프트웨어가 체크섬을 확인하거나 변경시 실행되지 않는 데이터가 존재하는 경우 Code Coverage를 충분히 확보할 수 없습니다.
Smart Fuzzing
Dumb Fuzzing과 다르게 Smart Fuzzer는 입력에 대한 구조를 미리 파악하고 테스트 케이스를 생성합니다.
예를 들어 XML Fuzzing을 진행할 경우 XML의 데이터 구조를 파악하고 XML File Format에 맞춰서 퍼징을 진행하는 것 입니다.
Guided Fuzzing
Test Case의 생성을 Code Coverage를 넓히기 위해서 사용하는 방법이며, Test Case에서 변경할 데이터를 정하고 그 부분의 데이터만 변경하는 것입니다.
Code Coverage는 Binary의 여러 로직을 얼마나 많이 거쳤는지에 대한 척도이며 Code Coverage가 높다는 말은 더 많은 로직에 Test Case가 입력되었다는 것이며 이는 곧 버그를 찾을 가능성도 더 높아진다는 이야기입니다.
Fuzzing Tools
Sanitizer
C, C++로 작성된 프로그램에서 발생하는 취약점에 대한 정보를 로그에 저장하고 보여주는 기능을 합니다.
AddressSanitizer
AddressSanitizer는 아래의 버그를 탐지합니다.
•
Use after free (dangling pointer dereference)
•
Heap buffer overflow
•
Stack buffer overflow
•
Global buffer overflow
•
Use after return
•
Use after scope
•
Initialization order bugs
•
Memory leaks
ThreadSanitizer
ThreadSanitizer는 Data Race를 탐지하며 두개의 Thread가 동일한 Data에 Access중 Data 수정이 될 경우를 탐지한다고 보면 됩니다.
MemorySanitizer
MemorySanitizer는 초기화되지 않은 메모리에 접근할 경우를 탐지합니다.
Radamsa는 Sample File을 기반으로 자동으로 Mutation을 진행합니다.
Mutation-Based Fuzzer로 분류되고 있습니다.
Code Coverage-Based, Mutation-Based, Dumb Fuzzer입니다. 입력에 대한 구조를 알 필요가 없지만, 해당 Fuzzer에서 자동으로 데이터 구조를 파악해 Mutation을 진행하는 특징을 가지고 있습니다.
데이터 구조와 변경할 데이터를 지정해 Fuzzing을 진행하는 Smart Fuzzer입니다.