- 커버리지에 대한 의미는 이전 포스팅에 간략하게 정의했으니 넘어가겠다.
- 퍼징을 진행하는데 있어 커버리지 측정에 대한 개념이 제대로 잡히질 않아 몇 가지 의문이 들었다.
- 커버리지 측정 계산은 어떻게 하는 것일까?
- coverage guided 퍼징 시 커버리지 측정이 어디까지 되는것인가?, 어느 부분이 필요한가?
- Windows 프로그램을 예시로, A회사의 프로그램을 퍼징하면서 WINAPI의 취약점을 찾을 수 도 있는 것일까?
- 조사를 통해 의문에 대한 나만의 답을 찾았지만, 잘 못 이해한것일 수 도 있으니 참고만 하자.
환경
- 커버리지 측정을 보기위해 Windows 환경에서 DynamoRIO와 Lighthouse를 사용하였다.
- 구축 방법은 공식 홈페이지를 참고하면 된다.
- DynamoRio : https://dynamorio.org/page_releases.html
- Lighthouse : https://github.com/gaasedelen/lighthouse
실습
- ipconfig 바이너리를 가지고 코드 커버리지를 측정해 보겠다.
- 아래 명령어를 통해 커버리지 로그를 얻을 수 있다.
1 | cd C:..\DynamoRIO-Windows-9.0.1\bin64 |
1 | DRCOV VERSION: 2 |
- 로그의 일부분으로, 바이너리가 실행에 필요한 모든 dll들을 로드하며, 거기에 대한 커버리지를 측정한다.
- IDA로 커버리지 확인을 할 바이너리 혹은 DLL을 열고, Lighthouse를 통해 커버리지 로그를 불러오면 아래와 같이 확인할 수 있다.
- (Ipconfig.exe에 대한 커버리지 확인)
- block 단위로 커버리지를 측정하는 것을 확인할 수 있다.
- 또한 전체 블록 중 몇 개의 블록을 커버했는지 확인할 수 있다.
- 이를 바탕으로 커버리지를 수치화하는 방법은 (커버한 블록 / 전체 블록) 으로 계산하는 것 같다.
- 1번 의문은 커버리지를 측정하기 위해 인스트루먼트 단계에서 전체 블록에 대한 값을 얻을 수 있고,
- 커버한 블록 갯수 역시 얻을 수 있기에 이 둘을 활용하여 계산 하는 것으로 확인했다.
- 2번, 3번 의문을 풀기 위해 예시를 들어보겠다.
- 프로그램 A, A.dll, Windows.dll 세 개의 파일이 있다고 가정하자. (프로그램 마다 다르다.)
- 프로그램 A는 A회사에서 만든 모듈로, 코드에는 A.dll에 있는 함수를 호출해서 사용하거나 자체적인 함수가 내장되어 있다.
- A.dll은 A 회사에서 만든 자체 함수들이 정의되어 있다. 여기 있는 함수는 Windows에서 제공하는 WINAPI를 바탕으로 만들어진 함수이다.
- Windows.dll은 WINAPI 함수들이 정의되어 있다.
- 전체적인 흐름은 아래와 같다.
1
프로그램 A (A.dll에 있는 함수 일부 호출) -> A.dll(자체함수 정의 부분, 내부적으로 WINAPI 호출) -> Windows.dll(WINAPI 함수 정의부분)
- 여기서 드는 의문은 과연 코드 커버리지는 어디 까지 측정되며, 어디 까지 측정 해야 할까. 그리고 프로그램 A를 퍼징한다면 결국 Windows.dll에 있는 WINAPI에 대한 취약점도 찾을 수 있는 것인가 이다.
- DynamoRio를 통해 확인 한 결과, 커버리지는 전체 다 측정 되는 것으로 확인할 수 있다.
커버리지 로그를 보면 바이너리 실행 시 로드 되는 모든 dll에 대해 인스트루먼트를 진행하고, 커버리지를 측정하는 것을 확인할 수 있다.
(로드된 dll에 대한 커버리지)
- 그럼 A 회사 제품에 대한 퍼즈 테스팅을 위해서라면 어디까지의 커버리지 정보가 필요할까
프로그램 A에 대해 측정한다면, 발생하는 버그는 A.dll에 있는 함수들을 잘 못 사용해서 발생하는것으로, 함수 호출 순서나 방법을 고치면 된다. 혹은 프로그램에 정의된 함수의 로직이 잘 못되어 발생하는 경우이다.
A.dll에 대해 측정한다면, 발생하는 버그는 Windows.dll의 WINAPI들을 잘 못 사용해서 발생하는 것으로, A.dll의 함수 로직을 고쳐야한다.
- Harness 프로그램을 dll에 있는 함수를 가지고 짠다면, 커버리지를 해당 dll을 측정해야 한다.
Windows.dll에 대해 측정한다면, 발생하는 버그는 WINAPI 함수의 로직에서 발생하는 것이다. 즉 A 회사 제품에 대한 취약점을 찾는 것이 아니다. WINAPI 함수 로직을 고쳐야한다.
- 또한 보통 3번의 경우는 흔하지 않아 보인다.
결론을 내리면, 의문 2는 퍼즈 테스팅 대상에 따라 다르다고 볼 수 있다. 공격 벡터를 정하고, 해당 기능을 처리하는 부분을 대상으로 퍼즈 테스팅을 진행하기에, 해당 부분의 커버리지가 필요하다.
그 부분이 프로그램에 정의 되어 있다면, 프로그램에 대한 커버리지가 필요하고, dll로 따로 정의 되어있다면 dll에 대한 커버리지가 필요하다.
WinAFL 퍼저의 경우 커버리지 측정 모듈을 지정해주는 옵션이 있어 필요한 커버리지만 얻을 수 있다.
의문 3은 흔하지 않은 경우로 생각 된다. A 제품에 대한 보안 검사로 A회사가 자체적으로 만든 프로그램이나 dll에 대해서만 퍼즈 테스팅을 진행하고, 커버리지를 측정하면 될 것 같다.
결론
바이너리와 실행시 로드되는 DLL 전부 인스트루먼트를 진행 하고, 커버리지를 측정한다.
퍼즈 테스팅 대상에 따라 필요한 커버리지 정보가 다르다.