반응형

Syzkaller를 활용한 fuzzing에 대한 내용은 언젠가 따로 해둘 것이며 본 내용은 code coverage에 대해 아래의 reference들을 통해 정리 및 번역해둔 것이다.

[+] 아직 지식이 많이 부족하기에 글의 오류가 있다면 지적에 주시면 감사하겠습니다( _ _ ) .

[+] 더욱 알아가며 내용을 보충해 보겠습니다.

 

Kernel fuzzing을 수행해보면, dashboard에서 아래와 같이 많은데  coverage에 대해서 본 글에서는 정리할 것이다.

기본적인 상태에서는 filtered coverage가 안뜨고

아래와 같이 customizing했을때, filtered coverage가 뜬다. 그리고 filtered coverage의 파일쪽으로 control flow graph(이하 cfg) 가 형성되는 것을 볼 수 있다. 

본인의 경우 특정 1-day보고서를 작성하며 fuzzing을 통해 생성되는 report를 직접 눈으로 확인해보고 싶어서 위와 같이 구성하여 시도해보는 중이었다.

하지만, 이러한 목적성 외에도 linux kernel내의 cfg에 대한 이해도가 높다거나, 본인처럼 관심있는 특정 서브시스템 같은 것이 있다면 그 쪽에 대한 contributing을 목적으로 fuzzing을 수행하기 위해 사용하기에도 용이할 것 같다는 생각이 든다.

 

자 이제 저 많은 dashboard 중 coverage가 뭐냐! 라고하면 주로, 정적 분석 도구에서 사용되며 코드를 어디어디를 얼마나 순회했는지 체크할 수 있는 지표(?)라고 표현할 수 있을 것 같다. 

 

이제보니 위키피디아에도 다음과 같이 나와있다.

In computer science, test coverage is a measure used to describe the degree to which the source code of a program is executed when a particular test suite runs. A program with high test coverage, measured as a percentage, has had more of its source code executed during testing, which suggests it has a lower chance of containing undetected software bugs compared to a program with low test coverage. - wikipedia

뭐.. 내가 한말이랑 같은 말이다 ㅋㅋ

소프트웨어의 코드가 얼마나 실행되었는지 수치화시킨것이다.

그렇다면 Code Coverage는 대체 무엇을 기준으로 지표를 생성할까?

 

구문(statement), 조건(condition), 결정(decision)의 구조로 코드가 존재할 때, 이를 얼마나 순회(커버(?))했는가에 기반하여 측정된다.

 

구문(statement)

- 코드 한줄이 한번이상 실행된다면 충족된다.( linux kernel의 pipe.c파일을 예시로 설명)

{
        BUG_ON(pipe1 == pipe2);		//1

        if (pipe1 < pipe2) {	//2
                pipe_lock_nested(pipe1, I_MUTEX_PARENT);	//3
                pipe_lock_nested(pipe2, I_MUTEX_CHILD);		//4
        } else {
                pipe_lock_nested(pipe2, I_MUTEX_PARENT);	//5
                pipe_lock_nested(pipe1, I_MUTEX_CHILD);		//6
        }
}

가령 위의 코드를 기반으로 테스트한다 했을때, pipe1< pipe2를 만족하는 데이터를 사용한다면, if문의 조건을 만족하는 3,4번 line은 수행되고, 1,5,6번 line은 수행되지 않으므로 구문 coverage는 40%를 만족한다고 볼 수 있다.

 

조건(condition)

- 모든 조건식의 내부조건이 true/false를 가지게 된다면 충족된다.

{
        BUG_ON(pipe1 == pipe2);		//1

        if (pipe1 < pipe2) {	//2
                pipe_lock_nested(pipe1, I_MUTEX_PARENT);	//3
                pipe_lock_nested(pipe2, I_MUTEX_CHILD);		//4
        } else {
                pipe_lock_nested(pipe2, I_MUTEX_PARENT);	//5
                pipe_lock_nested(pipe1, I_MUTEX_CHILD);		//6
        }
}

여기서 내부 조건이란 말그대로 조건식 안의 조건(위의 경우, pipe< pipe2)이라고 보면 된다.

위의 경우엔, test case가 "pipe1 > pipe2", " pipe1 < pipe2" 각각에 만족하는 경우라고 볼 수 있다.  "pipe1 = pipe2"의 경우 조건 문을 수행하지 않기에 제외해야 한다고 생각하여 뻈다

뭔가 이해하기 쉽게 하기 위해선 둘에 그냥 숫자를 대입해보는게 좋은 것 같다.

가령, (1) pipe1 = 1, pipe2 = 2  /  (2) pipe1 = 2, pipe2 = 1 이러한 식으로..?

 

대입해 보았을 때, (1) 경우는 3,4번 line이 수행되고, (2)의 경우는 5,6번 line이 수행된다. 이 경우, 조건 coverage를 만족하게 하는 test case 는 (1),(2)이다. 모두 true/false를 만족하기 때문이다. 

 

사실 조건 coverage는 아래의 링크에서 설명하신 예시로 이해하는게 훨씬 더 도움이 될 듯하다.

https://tecoble.techcourse.co.kr/post/2020-10-24-code-coverage/

 

왜냐하면 조건 커버리지를 만족해도, 구문 커버리지 혹은 이후에 설명할 결정 커버리지를 만족하지 못하는 경우가 존재하기에 이에 대한 예시로는 본인이 사용한 code가 적절하지 않아서이다. 이를 위한 예시로는 사용하신 x>0 && y<0이러한 것처럼, 조건이 두개 이상 존재하는 code가 더 적절해 보인다.

 

결정(decision)

- 모든 조건식이 true/false를 가지게 되면 충족한다.

{
        BUG_ON(pipe1 == pipe2);		//1

        if (pipe1 < pipe2) {	//2
                pipe_lock_nested(pipe1, I_MUTEX_PARENT);	//3
                pipe_lock_nested(pipe2, I_MUTEX_CHILD);		//4
        } else {
                pipe_lock_nested(pipe2, I_MUTEX_PARENT);	//5
                pipe_lock_nested(pipe1, I_MUTEX_CHILD);		//6
        }
}

위의 코드와 위에서 언급했던 (1), (2)예시의 경우 조건식에 대해 true혹은 false가 존재하므로, 결정 coverage를 충족하는 code라고 볼 수 있다.

 

언급한 3가지 code coverage구문 중에서는 구문 커버리지가 가장 대표적으로 많이 사용되며, 조건문이 존재하지 않는 경우, 결정/조건 coverage 의 대상에서 제외된다고 한다. 또한, 라인 coverage가 가장 많이 사용되는 이유는 모든 code에 대해 실행된다는 의미이며 물론, 모든 시나리오를 순회한다는 보장은 없지만, 어떠한 시나리오에도 문제없음은 보장될 수 있기 때문이다.

 

syzkaller에서는 이러한 coverage를 표현하기 위해 KCOV를 활용한다.

해당 옵션을 사용하기 위해서는 커널을 구성할 때, 아래의 문장을 넣어주어야 한다.

CONFIG_KCOV=y

또한, syzkaller은 sanitizer coverage도 같이 활용하는데, 해당 부분에 있어서는 아직 더 공부가 필요하여, 참고용 링크를 남겨둔다.

https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs

 

이제 다시 돌아와서 얘기해보면 syzkaller는 우선, compiler에 의해 object code(compiler나 assembler에 의해 컴파일 이후에 생성된 결과물) 룰 기반으로  coverage point들을 넣어 tracing한다. 이 coverage point는 basic block혹은 cfg의 edge(cfg의 마지막 부분)를 기반으로 생성된다.

[+] basic block은 본인의 이해 방식으로는 entry point외에는 들어오는 분기(branch)가 없으며, exit(return)외에는 분기가 없는 쭈우우욱 실행되는 코드 문자열이라고 보면된다.

[+] gcc의 경우 default가 basic block, clang/Linux의 경우 defualt가 cfg edge이다. 

 

여하튼, 이 coverage point들은 compiler에 의해 많은 변환 및 최적화 과정을 거친 후 삽입된다. 그리고 이는 당연하게도 소스코드와 많은 연관성을 가진다. 그리고 아래와 같은 모습으로 나타난 것을 볼 수 있다.

 

횟수야 뭐.. 아래의 글을 참고하여 code를 보면 어느정도 알 수 있겠지만, 사실 색 표시에 대한 정리를 위해 이 글을 작성하게 되었다.

https://www.kernel.org/doc/html/latest/dev-tools/kcov.html

 

커버리지의 지표를 색깔로 표시한게 상기의 사진에서 보인다. 물론 이 부분에 대해서도 build option에서 조절하면 더웃 정확한 수치를 확인할 수 있다고 한다.

그리고 색깔에 대한 정의는 아래의 code를 따른다.

https://github.com/google/syzkaller/blob/master/pkg/cover/report.go#L504

 

검정색, 주황색, 크림슨 레드(?), 회색, 빨간색  요런 색으로 표시된다.

아래의 순서대로 coverage 만족이 더 높은 순서로 numbering하였다.

1. 검정(Covered)

해당 라인이 coverage 를 만족한다는 의미(?)인 듯 하다. 왼쪽에 해당 줄과 관련된 pc값의 실행을 trigger한 프로그램이 몇개 있는지 보여준다. 그리고, 번호를 누를 시에 마지막으로 실행된 프로그램이 열린다.

 

2. 주황(Uncover && Cover)

몇몇의 PC값이 해당 라인과 연관있으며 모든 경우에 대해 커버리지를 만족하지는 못한다는 의미인 듯 하다. 즉, coverage를 만족하는 것과, 만족하지 못하는 것이 둘다 있는 경우를 뜻한다고 보면된다. 해당 색 역시 번호를 누를 경우 마지막으로 trigger할때 수행된 PC값들을 볼 수 있다. (block단위의 coveragepoint기준)

 

3. 크림슨 레드(빨강이지만 볼드체가 아닌것) (Weak- uncovered)

이 색의 경우 함수(symbol)가 어떠한 coverage를 만족하지 못한다는 의미이다.  즉 , 함수가 실행되지 않는다는 의미이다. 

하지만, 해당 라인은 컴파일 시 활용되는 부분이라고 볼 수 있다.

 

4. 빨강(볼드체) (Uncovered)

해당 라인이 coverage를 어떠한 것(라인에 대해/ 함수에 대해)도 전혀 만족하지 못한다.

 

5. 회색 ( Not instrumented)

해당 라인과 연결된 PC값을 전혀 확인할 수 없거나, code를 생성하지 않을 때 나타난다고 하는데, 이는 본인 생각에 전혀 callback(다른 함수의 실행 이후 실행되는 함수)이 이루어지지 않는 code들을 의미하는 것 같다.

 

-ref-

https://en.wikipedia.org/wiki/Basic_block

https://events.linuxfoundation.org/wp-content/uploads/2022/10/Andrey-Konovalov-Fuzzing-the-Linux-Kernel.pdf

https://tecoble.techcourse.co.kr/post/2020-10-24-code-coverage/

https://www.kernel.org/doc/html/latest/dev-tools/kcov.html

https://lwn.net/Articles/677764/

https://github.com/google/syzkaller/blob/master/docs/coverage.md

https://github.com/hardenedlinux/harbian-qa/blob/master/syzkaller/cover_filter.md

반응형
반응형

디버깅을 위한 준비하기

tool : KGDB, QEMU, BUSYBOX

제공된 요소: vmlinux, bzImage

https://jeongzero.oopy.io/73084e52-54fa-43e2-986b-072ee2a4f80d

 

상기의 링크를 바탕으로 디버깅 환경을 구성하였다.

환경: ubnutu 22.04

 

커널을 올릴 가상머신인 qemu와 dependency들을 설치해준다.( 본래의 글에서는 kernel-package를 설치하지만, 무슨이유에서인지 현재는 설치되지 않고, 설치하지 않아도 무방한 것으로 안다.)

sudo apt-get install qemu qemu-system
sudo apt-get install build-essential libncurses5 libncurses5-dev bin86 libssl-dev bison flex libelf-dev

 

주어진 요소가 vmlinux와 bzImage뿐이므로 rootfs파일을 구성해야한다.

이를 위한 busybox를 설치해야 한다.

필자는 위 링크의 구성대로 따르기 위해 v1.31.0을 설치하였다.

[+]v1.31.0에서 발생하는 에러로 인하여 최신 버전으로 변경하여 아래의 명령어들을 재수행하였다. (v1.36.1)

wget https://busybox.net/downloads/busybox-1.31.0.tar.bz2
tar -xvf busybox-1.31.0.tar.bz2
cd busybox-1.31.0
make defconfig
make menuconfig

이 때, menuconfig에서 아래와 같이 구성한다.

=================

build static binary 옵션 활성화
build debug information 옵션 활성화
그 밑에 disable compiler optimazations뭐시기 비활성화(어셈언어를 컴파일러가 날림)

 

=================

후에

make busybox
mkdir _install
make CONFIG_PREFIX=_install install

cf. static 옵션이 활성화되었는지 확인하고 싶다면, make defconfig이후에 구성된 .config파일을 확인하면 된다.

하지만 make CONFIG_PREFIX ~~~~수행 이후에 권한 관련 에러가 발생하여

make clean 명령 후

보안상으로는 좋지 않지만 chmod +s /bin/busybox 를 수행 이후에 다시 make하였다.

You will probably need to make your busybox binary
setuid root to ensure all configured applets will
work properly.

그래도 위의 에러는 해결되지 않았다...

일단 이후의 내용 계속 수행해보기로 결정

_install 디렉터리에 잘 생성된 것을 볼 수 있다.

그리고 올린 커널에서  실행할 파일이 있으므로

같이 넣고 디버깅을 하기 위해 local에 있는 pc에서 copy하여 추가해주었다.

그리고 cpio명령어를 통해 압축하여 rootfs.img.gz를 생성하였다.

find . | cpio -H newc -o | gzip > rootfs.img.gz

상기의 img.gz는 뭔가 불안정해보여서 그냥 .cpio파일로 짰다.

(아래의 명령어 참고)

find . | cpio -o --format=newc > rootfs.cpio

하지만 여전히 rootfs파일에 뭔가 문제가 있는 듯하다.

 

[+] 하하 뻘짓하고 있었다 ㅎ..

내 스크립트 파일의 오타로 인하여 안되는 것이었다.

 

디버깅을 위해서는 -append부분에 kgdbwait 옵션을 추가해주고 다른 콘솔에서 pid를 활용하여 붙으면된다.

qemu-system-x86_64 \
        -m 50G \
        -smp 2 \
        -cpu host \
        -kernel /home/hackin/kernel_study/bzImage \
        -append "console=ttyAMA0 root=/dev/sda nokaslr earlyprintk=serial net.ifnames=0" \
        -drive file=/home/hackin/kernel_study/bullseye.img,format=raw\
        -initrd /home/hackin/kernel_study/busybox-1.36.1/_install/rootfs.cpio \
        -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10022-:22,hostfwd=tcp:0.0.0.0:8080-:8080 \
        -net nic,model=e1000 \
        -enable-kvm \
        -nographic \
        -pidfile vm.pid \
        2>&1 | tee vm.log

 

반응형

+ Recent posts