반응형

이번 문제는 register의 구조에 대해 먼저 파악해야한다.

<1>

기존에 문제들은 레지스터의 모든 비트를 다 활용하여 문제를 풀어왔다.

하지만 이번엔 한 레지스터의 특정 byte만을 사용하여 풀기 위해 <1>의 사진을 참고해야 한다.

우리가 사용하던 rax는 MSB ~ LSB 즉, Most Significate Bit ~ Lesat Significant Bit로 이루어져 있다.

쉽게 말하면 가장 높은 위치의 데이터 ~ 가장 낮은 위치의 데이터이다.

 

위와 같이 이름이 붙은 이유는 말그대로 가장 중요하기에 덜  변화가 생기고, 덜 중요하기에 많은 변화가 생기기 때문이다.

가령 MSB는 일반적으로 부호형에 쓰인다.

부호가 자주 바뀐다고 생각해보면 값의 변화에 매우 치명적이기에 덜 바뀌어야한다.

 

LSB의 경우, checksum 등으로 쓰인다.

자주바뀌어도 MSB보다 덜 무방하기 때문이다.

 

RAX를 뜯어 보면 하위 32 bit는 EAX, EAX의 하위 16bit는 AX, AX의 상위 8bit는 ah, 하위 8bit는 al로 이루어져 있다.

 

이를 인지하고 문제를 보자.

mov instruction하나만으로 값을 세팅하라고 한다.

간단히 다음과 같이 짜주면 풀린다.

.global _start
.section .text

_start:
        mov $0x42, %ah

 

다음문제를 보자.

이번엔 이전 문제에서 배웠던 mod연산과 register에 대해 인지하고 있다는 전제가 필요하며 추가적인 정보를 알려준다.

요런식으로 나와있는데 한번 읽어보고 뭔소리인가 싶었다 처음엔...

해석해보자면,  만약 y가 2의 거듭제곱이라면 (y = 2^n), x % y의 결과는 x의 하위 n 비트와 같다는 말이다. 즉, x % 256이면 결과는 x의 하위 8비트와 같으며, x % 65536이면 결과는 x의 하위 16비트와 같다.

 

더 자세히 설명해보자면, 이는 2의 거듭제곱으로 나눌 때 발생하는 특이한 상황인데, 예를 들어 "256(2^8)로 나누는 경우, 나머지는 항상 8비트 이하의 값이 된다"는 것은 나머지는 항상 나누는 수보다 크지 않다는 개념을 적용하여 생각해보면 매우 쉽다. 

 

상기의 연산 방법이 일반적인 나머지 연산에 비해 빠르다고 한다.

일반적으로 나머지 연산은 단순한 나눗셈보다 더 많은 하드웨어 리소스를 소비한다고 하는데... 아마 이전에 Assembly crash에서 볼 수 있듯 레지스터의 사용이나 명령어의 수가 늘어나기에 나오는 말 같다. 하지만, 상기의 방법은 간단한 명령만으로 처리되기에 효율적이며 성능상의 이점을 가지고 있다.

 

위의 내용을 인지하고, 문제를 접근해보자.

mov연산만을 사용하라고 한다.

처음엔 대체 어떻게 mov 만을 사용할지 감이 안왔는데, 우선 아래의 사진을 봐야한다.

https://qpakzk.gitbooks.io/operating-systems-development/content/book/chap03.html

좀 더 세부적인 레지스터의 사진이다.

 

상기에서 언급한대로 자신의 하위비트보다 클 수 없으므로 하위비트가 나머지가 된다. 그렇기에 그 하위 비트의 값을 세팅해주면된다.

 

그렇기에 코드는 아래와 같다.

.global _start
.section .text

_start:
        mov %dil, %al
        mov %si, %bx

 

 

mov %dil, %al : rdi의 하위 8비트를 al 레지스터로 복사한다. %dil은 rdi 레지스터의 하위 8비트를 가리키는 것이다

 

mov %si, %bx : rsi의 하위 16비트를 bx레지스터로 복사한다. %si는 rsi레지스터의 하위 16비트를 가리키는 것이다. 

 

 

다음 문제를 위해 인지할 점은 1byte는 8bit라는 것만 알고 있으면 된다.

그리고서 양쪽으로 shift해준 후에 RAX에 삽입해주면 풀리는 문제이다.

 

코드는 아래와 같다.

.global _start
.section .text

_start:
        mov %rdi, %rax
        shl $24, %rax
        shr $56, %rax

그러면 쨘!

 

반응형

'pwn.college' 카테고리의 다른 글

Introduction to ARM - level 1 ~  (0) 2024.03.03
Read file in Linux  (0) 2023.12.04
HTTP 요청 보내기  (0) 2023.12.03

반응형

 

 

1. curl

GET 요청 

curl http://example.com

 

HOST헤더 넣기( -H옵션) + url인코딩하여 보낼 경우에 Host가 들어가야한다.

curl -H "Host: example.com" http://your-url

원하는 경로로 보내고 싶을때,

curl https://example.com/path

JSON형식으로 데이터 전달(-d옵션: data)

curl -X POST -H "Content-Type: application/json" -d '{"a": "value"}' http://example.com/path

 

POST요청

curl -X POST -d "param1=value1&param2=value2" http://example.com/resource

 

쿠키알아내기

curl -v --cookie "USER_TOKEN=Yes" http://127.0.0.1

쿠키정보를 file에 저장하고, redirect를 따라가는 명령

curl http://localhost -v --cookie-jar file -L

 

참고하기에 좋은 사이트

-https://reqbin.com/req/c-bjcj04uw/curl-send-cookies-example

 

2. nc

GET요청(Host헤더 포함)

cf. 이 때, 이유는 모르겠으나, \r\n\r\n 이게 꼭 두번 들어가야지 전달된다.

-> 우선 \r\n은 어떠한 데이터의 끝맺음을 의미한다. \r\n이 도착하지 않는다면 서버는 계속 요청 대기 상태가 된다.

경로를 지정하고 싶다면, / 이후에 추가

echo -e "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | nc example.com 80

json 형식

// vim req
GET / HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive
Content-Length: 길이{부터 }까지(괄호 포함)
Content-Type: application/json
User-Agent: curl/7.2

{"a": "value"}

호출

cat req  | nc localhost 80 -v

redirect

echo -e "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: keep-alive\r\nUser-Agent: curl/7.2\r\n\r\n" | nc localhost 80

 

POST요청

printf "POST /path HTTP/1.1\r\nHost: example.com\r\nContent-Length: 11\r\n\r\nHello World" | nc example.com 80

 

쿠키 알아내기

GET /setcookie HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive
User-Agent: curl/7.2

 

3. python request 모듈

GET요청(Host헤더 포함)

import requests

url = "https://example.com"
headers = {"Host": "example.com"}

response = requests.get(url, headers=headers)

print(response.text)

80포트로 써야하는 경우

import requests

url = "http://example.com:80"
response = requests.get(url)

print(response.text)

POST요청

import requests

url = "https://example.com"
data = {"key1": "value1", "key2": "value2"}

response = requests.post(url, data=data)

print(response.text)

 

반응형

'pwn.college' 카테고리의 다른 글

Introduction to ARM - level 1 ~  (0) 2024.03.03
Assembly crash level 7~9  (0) 2023.12.14
Read file in Linux  (0) 2023.12.04
반응형

매번 정적 분석만을 주로하다가 동적 분석의 중요성을 느끼게 되었다.

해당 파일은 stripped 되어 있어서 정적 분석을 수행할 수가 없다.

 

그러므로 ida에서 서버 파일을 리눅스로 옮겨 실행시킨 후, 로컬에서 동적 디버깅을 수행해보았다.

 

 

문제의 discription에서

이렇게 나와있으므로 적절한 위치에 breakpoint를 걸어준 후, rax를 확인하면 된다.

사진의 아래쪽에 플래그가 나와있다.

이후에 이어서 바로 check_return_value문제도 풀어봤다.

해당 문제역시 상기의 문제처럼 동적 디버깅으로 풀어야한다.

이전에 수행했던대로 똑같이 파일을 올려서 디버깅을 진행해봤다.

같은 방식으로 플래그를 구하면된다.

원래 ghidra  실습용으로 낸 문제인듯 한데, IDA가 익숙해서 IDA로 우선 진행했고, 추후에 ghidra로도 수행해봐야겠다.

반응형

'Reverse Engineering' 카테고리의 다른 글

Static Analysis vs Dynamic Analysis  (0) 2022.11.01
반응형

race condition을 활용한 kernel exploit에 대해 이해하기 위해 알아보다가 pm에게 자료를 받았다.

그리하여 본 글을 적게 되었다.

 

★본 글은 아래의 링크에 나오는 발표에 기반하여 정리하였고, 본인 메모용으로 작성된 글입니다.

★아직 미숙하여 설명이 모호하거나, 부족한 부분이 많습니다.

★주인장이 글을 시간날 떄마다 조금씩 써서 매우 자유분방합니다...양해좀해주세요

 

영상:  https://media.ccc.de/v/SHA2017-295-race_for_root_the_analysis_of_the_linux_kernel_race_condition_exploit#t=121

ppt: https://a13xp0p0v.github.io/img/Alexander_Popov-Race_for_Root_SHA2017.pdf

 


CVE-2017-2636 : https://nvd.nist.gov/vuln/detail/CVE-2017-2636

상기의 취약점은 linux kernel 내에서 발생하는 LPE취약점이다.

n_hdlc라는 kernel내의 driver에서 race condition이 트리거되며 모든 배포판에 있어 제공되고 load가능한 module이기에 모든 메이저 배포판들이 영향을 받았다(CONFIG_N_HDLC=m).

 

HDLC(High-Level Data Link Control)  driver는 현재는 device간의 연결을 지원을 위해 주로 사용된다. 해당 드라이버는 data link계층의 프로토콜이며, 각 frame들을 통해 serial line들을 전송한다.

 

본 취약점은 2009.06.22에 처음 소개되었으며 2017.02.01에 발제자의 syzkaller에서 detect되었고, 2017.02.03에 race condition repro가 생성되었다. 최종적으로 2017.02.28에 exploit PoC를 구현하였으며  patch로 적용되었다.

(이후에 나오는 일정은 해당 취약점에 대해 처리되는 일정...)

 

본래 HDLC 드라이버는 selt-made singly linked lists(단일연결리스트) 로 구현되어 data를 저장하기 위한 buffer로 구현되었다. 이 때의 데이터는 line(?)을 의미한다.

debuff 변수를 통해 buffer를 가리키는 pointer를 저장하고, 해당 pointer는 이미 전송된 위치에 대해 재전송(resend)하는 경우에 사용된다고 한다.

이때까지는 괜찮았으나, 이후에 be10eb75893 commit에서 buffer flushing이 추가되었다. 그리고 이로 인하여 race condition이 발생할 여지가 생겼다.

 

buffer를 flushing하는 function과 sending하는 function이 특정 buffer에 대해 free할 list로 두번 지정하게 된다. 이 상황으로 인하여 사용자가 terminal을 닫을 시에 insanely wrong locking(비정상 lock)이 발생하고, n_hdlc_release()에 의해 double-free가 발생된다.

 

이후에 exploit 환경에 대해 나오는데 SMAP는 적용되어 있지 않고, SMEP만 적용된 linux mint 4.4.0-53 generic환경에서 수행되었다.

 

소개된 exploit순서는 아래와 같다.

1. Prepare N_HDLC line discipline

2. Hitting the race condition to get double-free

3. Heap spraying for turning double-free into use-after-free

4. Another heap spraying to exploit use after free

5. Heap stabilization

6. SMEP bypass (without ROP)

 

우선 1번 과정에 대한 diagram이다.

사진 1

 

main logic의 line discipline에서 수행되며, master slave로 각각 생성된 terminal이 있다.

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

( 아마 추측하건데, tty로 전환해서 쓰는 터미널이 master terminal이고, usermode에서 직접 켜서 사용하는 terminal이 salve terminal인 듯하다..? 맞게 이해한건지 모르겠다.)

일단 GPT로 찾아보니, 

'pty master'와 'pty slave'는 일반적으로 이러한 가상 터미널을 만들 때 사용되는 두 가지 연결된 파일 디스크립터를 나타냅니다. 'pty master'는 터미널 세션의 마스터 엔드(master end)를 나타내며, 실제 사용자 입력 및 출력과 연결되어 있습니다. 'pty slave'는 터미널 세션의 슬레이브 엔드(slave end)를 나타내며, 응용 프로그램이 이를 통해 실제 터미널과 상호 작

뭐 이래 말해주긴하는데... 느낌상 내가 이해한 표현이 맞는 듯하다..! 아시는 분 댓글 좀요..

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

line discipline은 kernel code조각이며, backspace를 수행하거나 input에 대해 echo를 하는 로직( 즉 PIO(programmed input output)에 대해 terminal에서 xterm으로 돌아가는 것을 예시로 할 수 있다)이 존재한다.

 

cf. terminal vs xterminal

gnome에서는 기본적으로 xterminal이 깔려있지 않지만, 다른 OS에서는 xterminal이 깔려있는 것을 볼 수 있다.

일반적으로 그냥 terminal은 text 기반 환경에서 작동하며, xterminal은x window와 함께 사용되는emulator를 의미한다.

해당 부분에 대해서는 필자는 사용해 본 적이 없어서 자세한 사항은 따로 조사해봐야겠다.

 

여하튼 이후에 vulnerable driver와 hdlc driver hdlc discipline을 제공하고 있으며, 이를 활용하여 exploit을 수행한다.

 

exploit 구현을 위해 단일 CPU를 사용하여 모든 buffer의 할당이 하나의 할당된 cache를 활용하도록 하였다.

(추측이지만, 일부러 하나의 cache에서 사용하여 racy 환경을 더 쉽게 구축하기 위해서 이렇게 구성한 듯 하다.)

 

이후에 pseudotermninal master/ slave 쌍을 생성한다.

ptmd = open("/dev/ptmx", O_RDWR);

pseudoterminal(이하 ptmd)이란, 가상 터미널(?) 디바이스의 한 형태로써, 프로세스간 통신, 터미널 세션 모의화, 디버깅 등의 역할을 수행한다고 한다. 아마 해당 터미널은 생성된 pty master 와 pty slave라는 두가지 연결된 fd를 사용하여 가상 터미널을 제어하고 데이터를 전송하는데 사용하기 위해 생성하는 것 같다.

 

공부할거 산더미다 ㅎ...

 

이제 ptmd를 위한 line discipline을 세팅한다.

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

그냥 넘어가려 했는데 자꾸 나와서 한번 정리해야겠다.

line discipline은 터미널/시리얼 통신과 관련된 개념이며, 데이터의 흐름 혹은 문자 데이터의 처리를 제어하는 

데에 사용된다.

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

이렇게 세팅하면, vulnerable module(n_hdlc.ko)는 자동으로 생성된다고 한다.

const int ldisc = N_HDLC;
ioctl(ptmd, TIOCSETD, &ldisc);

 

이로써 exploit을 위한 환경 구성이 대략 끝난 것 같다.

 

이제 위에서 언급하였듯 race condition은 sending과 flushing이 이전에 보낸 영역(?)에 대해 수행되는 경우 발생된다. 그리고 이러한 상태를 만들어주기 위하여 terminal에서 현재 출력을 일시 적으로 중단하거나 일시 정지시켰다(일반적으로 ctrl+S로 출력 중단, ctrl + Q로 출력 재시작).

ioctl(ptmd, TCXONC, TCOOFF); //suspend the pty output
write(ptmd, buf, size); //write one data buffer(saved in n_hdlc.tbuf)

전송에 실패한 buffer에 대해 재전송하기 위해 사용되는 pointer(buffer를 가리키는) 를 tbuf에 변수에 저장한다.

 

이제 진짜 끝난거 같다 ㅋㅋ. 그리고 exploit이 수행되는 스레드들을 모든 CPU에서 수행한다.

현재 두 스레드의 상태이다. 하나는 flush하고, 하나는 suspend(일시 출력 중지)되었다.

사진 2

 

첫 스레드는 flush하며 iocontrol(ioctl)을 수행한다.  다른 스레드는 저장된 debuff라는 racy변수를 재전송할 것이다.

 

다음의 상황이다.

사진 3

위의 상황은 lag(지연)을 발생시켜 exploit이 더 빨리 수행되고, race condition을 빨리 구현되게 한 모습이다.

이 때 두 스레드는 pthread_barrier에 의해 동기화가 진행된다.

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

pthread barrier는 여러 thread가 동시에 어떤 지점에서 모이는 것을 기다릴때 사용한다.

thread가 작업을 수행한 이후에 pthread_barrier_wait()를 호출하여 임계지점(barrier)에서 다른 thread를 기다리고,

모든 thread가 임계지점에 도달하면 다시 모든 thread가 동시에 해당 지점에서 실행된다.

즉, thread간의 동기화를 달성하고, 작업 속도를 조절할 수 있다.

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

상기의 사진 기준으로 임계지점은 sync 부분으로 보여진다.

사진처럼 flushing thread가 loop에서 spinning중일때, 즉 대기하면서 지속적으로 CPU시간을 소모할 때, 이미 다른 thread는  vulnerable driver와 통신을 이미 시작한 상태이다. 

그리고, 사진은 error지점에서 둘이 동시에 tbuf 변수(n_hdlc.tbuf)를 사용하게 되며 이 때, race condition이 발생한다.

 

해당 영상에서 발제자가 말하길 race condition은 위와 같은 상황에서 시스템에서 발생되는 상황이며 어떠한 동작의 순서에 의존하고 비결정론적인 상황(결과를 예측하기 어려운 상황)이다.  또한, 위 상황에서 tbuf변수에 대한 결과는 어떻게 thread collision이 발생하는지에 의존한다고 한다.

(어찌보면 당연한 말이다, 충돌이 적절한 시점에 이루어져야 하니까..)

 

본 취약점 구현  중 지연에 대한 부분은 아래와 같다고 한다.

사진 4

그리고 최대 지연시간은 50microseconds라고 한다.

 

이제 double free에 대한 부분이다.

아래의 도식을 따른다.

사진 5

1. 객체 A를 할당한다.

2. 할당한 객체 A를 해제한다. 이 A를 두번 해제하게되어 double free 에러가 발생한다.

3. A와 같은 크기를 가진 객체 B를 할당한다. 이때, B는 A가 해제되었던 주소와 같은 위치에 들어간다. kernel allocator는 

방금 해제된 영역에 대해 우선적으로 할당하려 시도한다. 가장 빠르게 access할 수 있기 때문이다.

4. 하지만 해당 위치는 double free가 발생하는 위치이고, B가 실질적으로 해제된다.

5. 이제 2번째 heap spray를 수행한다. 객체 X를 할당하는데, 이는 payload를 가지고 있으며 역시 사이즈는 B와 같기에 이전에 B가 해제되었던 위치에 들어간다.

6. 마지막으로 code가 B를 사용하려 할때,  X에 담긴 paylaod가 수행된다.

 

정리하면 첫번째heap spray를 통해 double free취약점을 use after free취약점으로 전환하는 것이다. B를 활용함으로써  use after free취약점을 이용하는 것이다.

 

흠... 뭔가 두루뭉슬한데 어렴풋이 감은 온다.

 

이제 다시 우리가 원래 보던 버퍼쪽으로 돌아간다.

n_hdlc_buf는 kmalloc-8192 slab cache로 할당되어 있다. 그리고 해당 cache를 대상으로 double free취약점을 이용하려한다. 이제 우리는 두가지 kernel 객체를 필요로 한다. 첫번째 heap spray에 활용될 function pointer와 이 function pointer를 덮어쓰고 shell code를 수행할 payload가 필요하다.

이때, 사용할 버퍼로는 sk_buff(socket buffer)가 가장 적당하다고 한다. 우리가 exploit에 활용하고, 덮어쓸 수 있 첫번째 function pointer를 가지고 있으며, cache를 할당받을 수 있고, network frame들을 저장할 수 있는 객체로 linux kernel에서 사용된다. sk_buff는 그리고 meta정보를 저장하고 있게된다.

 

하지만 첫번쨰 heap spray과정이 쉬운 과정은 아니다. double free되는 부분에 있어서 n_hdlc_release()는 13개의 n_hdlc_buf 를 바로 한번에 쉬지 않고 해제한다. hdlc driver내의 free list 내에는 13개의buffer들이 존재하고, 모든 buffer는 allocator에 의해 관리 된다. 우리가 double free에 사용할 요소는 free list에 앞부분에 존재하고 원하는 요소를 바로 찾아내서 사용하기에 쉽지 않다. (하긴 어떻게 알고 바로 띡하고 찾아내냐 ㅋㅋ)

 

발제자는 buffer를 구분하기 위해 free되는 요소들에 대해 data를 사이사이에 추가해주려는 것 같았으나 실패했다고 한다.

슬라이드는 요로코롬..

사진 6

실패 이유에 대해서는 single CPU에서는 interrupt과정이 전혀 없으므로 사이사이에 추가할 수 없다고 한다....!

 

결국, 해당 부분은 아마 interrupt가 사이사이에 발생될 때, data를 추가해서 구분지어야하는데, interrupt가 없으므로 추가를 못한다는 말로 정리할 수 있을 것 같다.

 

다시 ppt로 돌아가서, buffer에 대한 free()가 포함된  n_hdlc_release()는 kernel에서 crash를 유발하지 않는다고 한다.

이는 즉 SLUB allocator가 buffer(same address)에 대한 연속적인 free()동작에 있어서 허용을 해준다는 말이다.

(이전 ppt에서 13번 n_hdlc_buf에 대해 해제 하는 내용 상기)

 

그리하여 발제자는 n_hdlc_release이후에 heap spray를 수행해 두면... free list로 부터 buffer를 하나씩 가져올 수 있고, 

최종적으로 아래와 같은 상황이 벌어진다.

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

그전에 잠시 heap spray에 대한 설명을 그냥 하고 넘어가야할 것 같아서 적어둔다.

 

kernel heap spray: kernel heap영역에 원하는 데이터를 spraying하는 기법, 이 기법은 직접적인 영향을 주지는 않지만 몇몇 경우에 따른 exploit의 수행에 있어서 필요할 때가 있다.

 

유저 공간에서는 kmalloc()를 호출할 수 없기에, 다양한 syscall을 이용하여 kmalloc()을 간접적으로 호출

 

kernel heap spray의 조건1. 유저가 원하는 크기의 slab object 할당 가능2. 유저가 원하는 데이터를 할당한 slab object에 복사할 수 있음3. 할당한 slab object에 불필요한 header data가 없아야함4. exploit이 끝날때까지 spraying한 slab obhect가 해제되지 않음

 

-ref-

https://www.inflearn.com/course/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90-%ED%95%B4%ED%82%B9/ -> ㄹㅇ 킹갓 강의.. 돈이 아깝지 않음 

 

heap spray시 유의 사항: malloc()은 할당할 Heap의 크기가 0x20000(128 * 1024) 이상일 경우 Heap 영역을 확장하지 않고 mmap을 사용하여 새로 할당 된 메모리 영역에 데이터를 저장

 

-heap spray ref-

https://www.youtube.com/watch?v=Ec4UEtO7dPI -> window OS를 기반으로 설명하였지만, 이해하기에 좋다.

https://duasynt.com/blog/linux-kernel-heap-spray

 

물론 본인이 기재해둔 링크들 외에 더 좋은 곳들도 많이 있을 것이다^^(난잡해서 여기에 다 기재안하기도 했고...)

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

 

사진 7

같은 데이터를 가리키고 있는 두개의 socker buffer를 가지고 있는 경우, 둘중에 하나의 buffer를 받게 된 이후에, 남은 buffer를 user-after-free취약점을 위해 사용할 수 있기 때문이다.

 

double-free취약점에서 user-after-free취약점으로 이어지게 하기 위해 8KB의 사이즈를 가진 UDP packet들을 race이후에 생성하였고, kernel memory에 저장하게 하였다.

하지만 이 과정은 network packet들을 위한 socket queue의 사이즈가 제한적이기에 쉽지 않은 과정이라고 한다.

이 제한적 상황으로 인해 overflow가 발생하지 않도록 많은 socket queue들을 만들어서 저장하였다고 한다. 그리고 한 쌍의 socket buffer를 받아 use-after-free취약점을 유발하였다.

 

heap spray의 수행은 아래의 사진과 같다.

사진 8

우선, heap spray를 위한 200개의 server socket들을 생성한 후에 전송한다. 이때,  어떤 패킷이 동일한 데이터를 가리킬 가능성이 있는지 경험적으로 안다고 한다.....(이걸 어케앎;;;) 

암튼 전용 server socket으로 전송하여 use-after-free error가 발생하고, 전용 서버로부터 몇몇 패킷들을 받은 뒤, allocator의 상태를 초기의 위치(?)로 다시 돌려둔다고 한다. 아마 위의 사진처럼 한번에 너무 많은 데이터를 받아 처리할떄, slab allocator가 고갈되어 kernel crush가 발생할 수 있으므로, 이를 방지한다는 얘기인듯 싶다.

 

이로써 첫번째 heap spray과정이 끝났다.

이젠 한쌍의 패킷들을 받아 LPE(Local Privilege Escalation을 위한 overwriting수행한다.

 

heap spraying #2(사진 5 참고)는 socker buffer내의 destructor_arg를 덮어쓰기 위한 과정이다. 하지만, 다른 socket buffer를 대상으로는 할 수 없다고 한다. 

그이유는 우리가 data로 덮어써야할 구조체 이후에 사진 7을 참고하여보면, skb_shared_info 구조체가 바로 나오며, skb_shared_info구조체의 descturctor_arg와 우리가 활용하는 sk_buff구조체의 .head가 둘다 시작으로부터 같은 offset에 위치하므로, 조작하지 않는다고 한다.

 

그니까 결국 offset이 겹쳐서 조작하기 힘드니까 sk_buff로 destructor_arg까지는 overwrite하지 않고, 다른 kernel 객체로 destructor_arg를 overwrite한다는 말이다.

 

이때 활용되는 것이 add_key syscall이다.

add_key syscall을 통해 payload를 커널 메모리에 저장할 수 있고, cache에 할당할 수 있음을 알게 되었다고 한다.

(상기의 heap spray 에 대한 reference 중 inflearn강의에서도 add_key syscall을 사용하시는 것이 나왔던 것으로 기억한다.)

// linux/net/core/skbuff.c in skb_release_data()

if (shinfo->tx_flags & SKBTX_DEV_ZEROCOPY) {
	struct ubuf_info *uarg;
	uarg = shinfo->destructor_arg;
    if (uarg->callback)
	uarg->callback(uarg, true);
}

위의 코드는 데이터 버퍼를 해제할 때 호출되며, 특정 조건이 충족되면 SKBTX_DEV_ZEROCOPY 플래그를 사용하여Zero-Copy 모드로 전환되어 있는 경우 호출된다. shinfo 구조체 에서 destructor_arg field를 통해 uarg 구조체를 얻는다.

이후에 callback이 수행된다.

즉, 네트워크 패킷 전송에 있어서 zero-copy 모드에서 데이터를 해제할때의 작업 수행에 대한 부분이다.

 

고로 두번째 heap spray는 data를 overwrite하고, 상기의 특수한 플래그를 넣어주고, destructor를 user space에 할당된 payload로 덮어 씌우는 과정이다. shell code역시 user space에 존재한다. 하단의 사진을 참고.

사진 9

 

상기의 사진에서 destructor_arg 포인터가 ubuf_info구조체를 역참조할 경우, SMAP에 의해 막힌다.

kernel의 포인터가 user - level에 접근못하게 막는 것이 SMAP이기 때문이다.

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

SMAP: 커널이 유저 레벨 메모리 접근 불가

SMEP: 커널이 유저 레벨 코드 실행 불가

(cr4 register, 뭐 자세한 설명 등등은... 기재 ㄴㄴ.. 이미 너무 길어서;;)

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

발제자의 exploit에서는 SMEP에 대한 bypass만을 다룬다.

우선 bypass이 수월하지 않다고 한다. key data의 할당은 root에 의해 조작되며 우리에게 주어진 payload의 size는 20000byte이기 때문이다.

 

root에 의해 조작해야하는데 어떻게 할까...? 싶다가 내용이 추후에 나오게 된다.

 

이 20000byte는 key syscall 수행을 고작 두번밖에 못하기에 spraying하기에는 턱없이 부족한 상태이다. 여기서 왜 두번 밖에 수행 못하냐면, 8KB 페이로드를 동시에 저장해야하기 때문이라고 한다.

(8KB에 대한 내용은 아마 이전에 다른 CVE분석에 언급되어 있을텐데.. 아마 우선 페이지가 주로 4KB이나 8KB이고, OS내에서 편의적으로 고려되는 사이즈이기에 그랬던것 같다.)

 

다음 슬라이드에서 좀 신기한 이야기를 한다.

heap spray의 성공은 add_key syscall의 호출여부에 의존하지 않는다고 한다. 그리하여 현재 root권한이 아닌 발제자가 add_key syscall을 수행하는 것이 실패하겠지만, 실질적으로는 payload가 kernel memory에 존재하며, socket buffer를 덮어쓸 수 있었다. 고로 호출을 실패해도 그냥 둔다.

 

이제 최종적인 spraying은 아래와 같다.

server socket을 대상으로 뿌릴 20개의 패킷을 만들고, 경험적으로 미루어 보았을때, 12, 13, 14, 15번째 패킷들은 각각 같은 데이터를 가리키는 상태가 될 것이라고 하였다.

12~15의 4개의 패킷들을 각각 하나씩 받아가서 overwrite을 위해 add_key syscall을 수행하게 한다. 최종적으로 recv() (==free()) 이후에 다시 초기 상태의 allocator를 저장하기 위해 추가적으로 15개의 패킷을 다른 서버로 보낸다. 이 수행은 exploit이 안정적으로 수행되게 도와주며, 방금의 15개의 패킷을 보내는 과정이 없을 시 krenel crach가 발생할 수 있다. 이유는 slab이 완전히 free되었을때, double free를 감지하기 때문에 이를 방지하고자 보내는 것이다. 이러한 기법을 "Slab exhaustion" 이라고 부른다.

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

Slab exhaustion에 대해 좀 더 안내를 하고 넘어가면, 

정확히는 "Slab이나 다른 메모리 할당 기법에서 할당된 메모리가 과도하게 소비되어 더 이상 사용 가능한 메모리가 없는 상태" 를 가리킨다. 그리고, 이러한 상황이 발생 시, Slab allocator는 새로운 page를 메모리에 할당해준다. 그렇기에 Slab Exhaustion은 새로운 페이지에 할당되는 구조 즉, 메모리 할당이 서로 다른 페이지에서 이루어지게 되어 메모리가 인접하지 않는 구조를 유지, 를 통해, 메모리가 완전히 해제된 후에 해당 메모리를 다시 할당하는 시도를 막는다. 이는 곳, double free의 발생을 막아주는 것이다.

 

한마디로, 안전한 exploit 수행을 위해 메모리 할당을 통제하고, 메모리가 완전히 해제되면서도 안정적인 상태를 유지하는 데에 사용된다는 것이다.

 

추가적으로 더 알아보고 싶은 부분은 아래의 논문을 보면 될 듯하다. 필자는 일부분만을 보았으며 추후에 한번 읽어볼 듯 싶다. heap layout에 대한 내용이 많이 있는 듯하다.

https://openreview.net/pdf?id=UqszJh5h6v

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

 

이제 key syscall에 대한 예시를 보여준다.

해당 부분은 우리의 payload가 kernel space에 저장되기 위한 공간이며 invalidation을 위한 두개의 add_key syscall만이 

성공적으로 수행된다.

 

최종적으로 정리하면 kernel이 user space에서의 명령어 수행을 실행할 때, 우리는 fault를 확인할 수 있다.

이후에 아래와 같이 SMEP에 대해 설명하는데

 

사실 SMEP와 SMAP는 각각 CR4 register의 20번과 21번 bit가 0이냐 1이냐로 on/off가 결정된다

그리고 여기서는 SMEP에 대한 bypass만을 설명하므로, 20번 비트를 0으로 만들면 꺼진다는 얘기만으로 간략하게 설명한다.

 

그리고 SMEP의 BYpass technique이 있다는 것을 알려주는데 많이 사용되는 기법이라고 한다. 필자도 overwriting한다는 개념만 알고 있었기에 추후에 한번 살펴볼 듯 싶다.

여하튼 이 두가지 기법은 AAW가 가능해야 한다는 제한적인 조건이다.

그리하여 발제자를 더 간단하게 SMEP를 bypassing하기 위한 더 쉬운 기법을 소개한다.

우선 커널 내에는 아래와 같이 이미 우리에게 필요한 함수가 존재한다.

해당 함수는 cr4 register를 0으로 설정할 수 있는 함수이다. 느낌상... 이를 이용하여 set상태의 register를 callback을 통한 동기화 때 호출하여 unset해줄 듯 하다.

 

이제 위의 native_write_cr4()가 어떻게 쓰이는지 알아본다.

(껄껄 역시나인가..?)

상기의 함수의 동작은 buf구조체의 주소를  signed long형태로 첫 인자로써 call한다.

그러므로 우리가 만약 native_write_cr4()를 ubuf_info.callback으로써 활용하는 경우, 즉 ubuf_info의 주소에  mmapped syscall로부터 받아온 특정 addr인 0x406e0를 삽인 하는 경우에 bypass가 가능하다.

uarg -> callback(uarg, true) == native_write_cr4(0x406e0)

 

발제자의 machine에서는 cr4레지스터의 SMEP에 해당하는 21번을 unset하는 데에 사용할 native_write_cr4()의 주소가 0x406e0이므로 해당 주소를 사용한 것이고, 결론적으로 ROP를 사용하지 않고도 SMEP를 bypass하게 된 것이다.

주솟값은 CPU에 의한  userspace와 x86의 CPU OID instruction를 통해 결정된다.

 

이로써 kernel에서의 exploit을 위한 환경은 완료되었고, SMEP가 disabled된 이후엔  shell code를 실행할 수 있는 두번째 race를 수행한다. 

 

이후부턴 patch에 대한 내용으로 이어진다.

본래에 vulnerable한 buffer는 자체적으로 개발한 구조체가 아닌 standard란 linked list를 사용하고 있었고, 발제자는 race condition을 유발하는 n_hdlc.tbuf를 제거하도록 하였다. 그리고 전송에 실패하는 경우, tx_buf_list를 현재에 있는 data buffer앞에 위치하게 하여 추후에 전송하도록 만들었다고 한다.

 

그리하여 현재의 n_hdlc구조체가 아래와 같은 형태를 가지게 되는 것 같고, 주석을 참고하여보면
보류 중인 전송 프레임 buffer list가 구조상 앞쪽에 있다.

/**
 * struct n_hdlc - per device instance data structure
 * @tbusy: reentrancy flag for tx wakeup code
 * @woke_up: tx wakeup needs to be run again as it was called while @tbusy
 * @tx_buf_list: list of pending transmit frame buffers
 * @rx_buf_list: list of received frame buffers
 * @tx_free_buf_list: list unused transmit frame buffers
 * @rx_free_buf_list: list unused received frame buffers
 */
struct n_hdlc {
	bool			tbusy;
	bool			woke_up;
	struct n_hdlc_buf_list	tx_buf_list;
	struct n_hdlc_buf_list	rx_buf_list;
	struct n_hdlc_buf_list	tx_free_buf_list;
	struct n_hdlc_buf_list	rx_free_buf_list;
	struct work_struct	write_work;
	struct tty_struct	*tty_for_write_work;
};

 

이젠 SLUB에 대한 patch이다.

이전에 잠시 언급되었듯 glibc에 의한 'double free or corruption(fasttop)' security check을 피하기위해 같은 객체에 대해 진행되는 두 free 사이에 다른 청크가 free된다.

a = malloc(10);     // 0xa04010
b = malloc(10);     // 0xa04030
c = malloc(10);     // 0xa04050

free(a);
free(b);  // To bypass "double free or corruption (fasttop)" check
free(a);  // Double Free

d = malloc(10);     // 0xa04010
e = malloc(10);     // 0xa04030
f = malloc(10);     // 0xa04010   - Same as 'd'

위의 예시는 fastbin으로 관리되는 경우에만 해당된다.

-ref-

https://oiehso0.tistory.com/entry/09Double-Free

이러한 우회를 막기 위해 다른 두 객체를 해제한 뒤, 주소를 검사하여 이전의 주소와 같지 않은지 검사한다. 위처럼 같은 주소를 가리키는 상황을 막기 위해서 보호기법을 적용한 것이다.

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

이로써 발표는 마치게 되고 나머지 내용은 Q&A 부분이랑 그외에 이것저것인데 간단히만 정리하였다.

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

Q&A(특히나 번역도 빡세고, 여기서 나온 키워드들을 기반으로 더 searching이 이루어져야겠다.)

Q: 해당 컨퍼런스가 발표되는 시점에서 좀전에 언급된 보호 기법이 mainline에 적용되지 않아있는데 이유는?

A: 이미 slub debug의 특징에는 비슷한 보호기법이 적용되어 있다고 한다. 그렇기에 중복되는 보호기법을 적용하고 싶지 않아하는 상황이지만, default kernel은 이러한 보호기법이 걸려있지 않다고 한다. 그렇기에 allocator는 double free에 대해 용한다.

또한, 다른 솔루션으로 제시된 것은 SLAB_FREELIST_HARDENED라는 kernel config option이다. 이 옵션은 freelist내부의 item들의 주소를 랜덤하게 바꿔주는 옵션이다. 이는 공격자가 heapoverflow exploit을 통해 kernel내의 포인터를 덮어서 다음 freelist의 포인터를 조작하려는 것을 어렵게 만든다. 공격자는 이 때,  고유한 장소에 저장되어 있는 쿠키를 통하여 유추해 내야하며, 발제자가 제시한 보호기법은 이 옵션에 포함될 것이라고 한다.

(글을 작성하는 현시점에는 slub allocator가 채택되어 default allocator로 사용되고 있다.)

 

다음질문은 architecture 자체에 대한 내용인데, 말이 상당히 빨랐어서 간단히만 얘기하면 user space와 kernel space가 mapping되어 있지 않아도 본 exploit이 수행될 수 있는지 여부를 묻는 것 같다. 답변으로는 발제자의 exploit은 userspace단에서 동작하는 프로그램이며 몇몇 systemcall을 수행하기에 kernel은 현재 실행되는 프로세스에 대한 정보를 알아야한다고는 하는데....

아직 더 알아봐야하는 부분인 듯하다.

 

나머지 질문들은 약간 여담의 느낌이라 한번 스윽 들어보는 정도여도 괜찮을 듯 싶다.

위양성에 대한 얘기도 나오는 것 같고...thread sanitizer에 대해서도 언급하고.. 그외 등등...

 

그리고 중간에 segmentation이라는 말이 있을텐데, 이는 메모리 접근 방식에서의 segmant 방식을 의미할 것이다.

 

 

회사다니면 거진 2주간 짬짬 열심히 작성하였고 많은 공부가 된 컨퍼런스였다.

 

띄엄띄엄 써서 글이 자유분방하지만... 여기서 얻은 키워드들을 바탕으로 더 공부해 나아가야겠다는 생각이 든다

 

-ref-

https://github.com/torvalds/linux/blob/master/drivers/tty/n_hdlc.c

https://jeongzero.oopy.io/132fed8f-5cfd-4f43-990c-61584744b4d0

그외 구선생님...♥

 

 

 

추후에 볼만한 링크도 함께 달아둬야지...(사실 내가 볼 용도 ㅎㅎ;)

OffensiveCon23 의 race condition exploit 파트이다.

https://www.youtube.com/watch?v=9wgHENj_YNk

반응형

'DEBUG Project(one-day analysis)' 카테고리의 다른 글

CVE-2021-34866  (0) 2023.06.02
CVE-2022-1103  (0) 2023.05.31
CVE-2021-3490  (0) 2022.12.15
정보보안 공부용 자료들 링크  (1) 2022.11.01
Integer overflow and heap overflow that occur sequentially in the BPF module  (0) 2022.10.21
반응형

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

 

반응형
반응형

내가 봐야할 문서들 정리..!

필요하신 분도 보면 좋을 듯

 

https://www.usenix.org/sites/default/files/conference/protected-files/osdi20_slides_nelson.pdf

https://www.usenix.org/system/files/osdi20-nelson.pdf

https://cdn.open-nfp.org/media/documents/demystify-ebpf-jit-compiler.pdf

 

최근에 접한 eBPF ppt

https://docs.google.com/presentation/d/1AcB4x7JCWET0ysDr0gsX-EIdQSTyBtmi6OAW7bE0jm0/edit#slide=id.g70356bf6e4_0_1128

 

여태 본 것들중 제일 정리가 잘 된 글이라고 느껴진다.

https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/

 

 

겸사겸사 늘 시도해보지만 맘대로 못다루는 IO Visor project의 블로그

https://www.iovisor.org/resources/blog

 

이전에 올린적 있는 사이트 인것 같으나 한번 더  올리기(낭낭함)

https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/

반응형

'ebpf' 카테고리의 다른 글

eBPF programming  (0) 2024.01.09
Troubleshooting - learning eBPF  (0) 2024.01.01
JIT(Just In Time) Compiler - Verifier - SAT/SMT solver  (0) 2023.09.17
Buzzer(eBPF fuzzer) build  (0) 2023.08.11
eBPF references - Blackhat  (0) 2023.08.06

+ Recent posts