SyzDirect: Directed Greybox Fuzzing for Linux Kernel(2)
이 논문은 진짜 이해가 잘 안가서 시간이 오래 걸렸다. 어짜피 결과에 대한 것들은 이렇게 연구해서~ 이렇게 나왔어요~ 보여주는 것 뿐이니까 해당 알고리즘에 대해서 노션에 정리한 것들을 올릴 예정.
노션에 정리한 글 중간 중간 약간의 나쁜 말들이 있어서 그것만 지우고 올렸다.. 다시 읽어보는데 정리는 잘 한 것 같다. 이제 해당 SyzDriect으로 실제 작동을 해보면서 얻어나갈 예정..
진입점 식별.
일단 퍼징을 하기 위해서는 진입점을 식별하는 것이 중요하다.
왜냐??. 처음 진입점 자체를 잘 못 잡으면 퍼징을 아무리 돌려도 의미가 없으니까!!
그래서 일단 본 논문에서는 Linux를 두 가지 특성을 내놓음.
- Syscall variant는 Syzlang 언어로 특정 리소스에 대한 특정 작업을 설명한다. 이는 기존에 있는 syscall 보다 더 많은 정보를 가지고 있다.
- Syscall 진입점에 들어간 후에는 다음 실행되어야할 함수를 결정하기 위해 디스패치를 진행한다.→ 4번만 3~4번 읽어보니까 Syscall variant라는 말이 계속해서 나오며, “resource”와 “ “operation”이 계속 해서 나올 것이니까 잘 이해하고 넘어가야한다.
- → 여기서 디스패치란?. 어떤 리소스가 처리되고, 어떤 작업이 수행되어야 하는지를 결정하기 위해 시스템 콜 인수를 파싱하는 것.
그래서 이 특징 가지고 뭐함?
저기 나오는 resource와 operation을 이용해서 통일된 방식으로 모델링을 진행할 거야.
모델링을 해서 뭐함?
모델링을 진행하면 시스템 콜 변형과 커널 함수를 매칭할 수 있어.
지금 나오는 저 흐름을 제대로 기억해야 4번을 따라갈 수 있다.ㅠ
앵커 함수.
갑자기 무슨 앵커 함수가 나오냐?. 위에 나오는 dispatch 부분때문에 나오는 것이다. 모델링을 할 때, 모든 함수를 직관적으로 모델링을 하기에는 시간이 많이든다..
→ 그래서 디스패치 프로세스를 분석하여 후속 코드에서 어떤 리소스가 조작되고, 어떤 연산을 수행하는지 이해한다.
→ 디스패치 후, 처음 실행되는 함수를 앵커 함수라고 한다.
연산 모델링.
Syscall 이름과 command parameter를 이용해서 커널 함수와 Syzlang veriant가 각각 리소스를 어떻게 처리하는지 모델링한다.
- 시스템 콜 변형 분석을 위해 Syzlang 설명에서 직접 syscall name을 extract.
- 그 후 parameters를 parse, 그 안에 numeric constants를 명령 값으로 사용한다.
- → 커널 함수 분석을 위해 Control flow and data flow를 활용하여 명령값 추출.
- syscall 이름을 활용하여 forward analysis를 통해 CFG에서 모든 switch 문을 찾는다.
- data flow analysis를 통해 switch문의 변수가 syscall 매개변수와 관련이 있는지 확인.
- 만약 switch가 분기→ case내의 함수를 앵커 함수로 사용하며, 해당 case의 상수 값을 명령 값으로 사용한다.
예시
- Syzlang에 있는 syscall name을 직접 extract하여 keyctl$update를 가져온다.
- 여기에 있는 code const[KEYCTL_UPDATE]를 파싱하여 KEYCTL_UPDATE라는 numeric constants를 명령 값으로 사용한다.
- →>> 이제 모델링을 진행할 때는, keyctl,KEYCTL_UPDATE로 진행하여
KEYCTL_UPDATE를 stich 분기분으로 활용하는 것을 찾는다..
→ 분기되는 처음 함수를 앵커 함수로 활용한다고 했으니까, 6번 줄 에서 분기가 되는 것을 확인하였고 처음에 있는 함수가 keyctl_update_key니까,
keyctl_update_key를 [keyctl, KEYCTL_UPDATE]로 분기한다!!
리소스 모델링
근데 리소스 모델링은 동일한 리소스가 커널 코드와, Syzlang 설명 간에 다르게 명명되고 정의될 수 있다.
그래서 리소스 이름으로 모델링할 수 없다
→ 본 논문은…. 리소스 생성 시 불변량을 사용하여 모델링할 것을 제안함!!!!
장치 및, 파일 시스템 리소스를 생성하려면 그 전에, 파일 시스템/ 장치 결로를 나타내는 상수 문자열이 필요하다!!!..
→ 리소스 유형을 지정하기 위해서는 리소스 생성과 관련된 문자열 또는 상수가 필요하다..
→ EX) fd 0, 1, 2를 지정해서 하는 것 처럼.
IF) Syscall 변경일 경우 ← 이거 자체가 Syzlang을 기반으로..
매개변수를 파싱하여 해당 리소스를 얻을 수 있다.
Syzlang 설명에서 sendmsg$rds는 2행에서 sock_rds라는 리소스를 입력으로 받는다.
sock_rds는 1행에서 여러 상수를 입력으로 받으므로.. sock_rds를 상수 목록 [AF_RDS, SOCK_SEQPACKEY]으로 모델링한다.
IF) Kernel Function 경우
간접 호출의 대상을 분석하여 필요한 리소스를 결정한다.
또!! 뭘 관찰함. 그게 뭐냐~ → 다른 유형의 리소스가. 고유한 가상 테이블과 유사한 데이터 구조에 할당되는 것을 관찰했다?..
- TCP 소켓
- tcp_prot_ops 같은 ops 테이블이 붙음
- 내부 엔트리들이 tcp_* 계열 함수로 구성 (예: connect/sendmsg/recvmsg 등)
- UNIX 도메인 소켓
- unix_stream_ops / unix_dgram_ops 같은 ops 테이블이 붙음
- 내부 엔트리들이 unix_* 계열 함수로 구성
- RDS 소켓
- rds_proto_ops 같은 ops 테이블이 붙음
- 내부 엔트리들이 rds_* 계열 함수로 구성
→ 커널은 자원 종류마다 서로 다른 ops(=vtable 비슷한 함수 포인터 테이블)를 붙여서, 같은 ‘소켓/파일’이라도 타입에 따라 호출되는 구현이 달라지게 만든다. 라고 하네요!!
그리고 또, 이제.. syscall entry.. 즉 진입점을 들어가고 나서부터는 커널은 간접 호출을 통해 함수 디스패치를 수행한다. 이 때! !!! 간접 호출은 리소스에 해당하는 가상 데이블을 querires해버림.
쿼리? 그게 뭐냐!!! 그냥 flow를 보면
- 간접 호출이 발생한다
- 그 간접 호출은 해당 자원에 연결된 vtable/ops 테이블을 참조해서
- 그 안에서 맞는 함수 포인터를 찾아
- 그 함수로 점프(호출)하므로
- 제어 흐름이 그 기능을 수행하는 코드로 이동한다
이렇게… 약간 참조!! 느낌. 그래서, 그 가상 테이블로 점프해서 거기에 있는 함수를 모두 앵커 함수라고 하는데..
but!! 리소스의 가상 테이블과 리소스 생성 시의 불변량 사이에는 간극이 존재한다 ..ㅠ
→ 자원 타입을 식별하는 단서로 ops/vtable을 쓰고 싶지만, 그 ops가 어떤 생성 상수(invariants)에서 비롯됐는지 직접 연결이 안 되어 있어서, 그 연결고리를 추가 분석으로 메워야 한다.
그래서 또 관찰 하고.. 분석해버림ㅠ
- 모듈 로딩 또는 커널 초기화 중에서 커널은.. 일반적인 등록 함수를 호출하는데, 이 등록 함수는 상수 정보 및 리소스 생성 함수와 같은 핵심 정보를 인수로 받는다.
with GPT로 예시 들기.
(비유: “(AF_RDS, SOCK_SEQPACKET) 조합이면 rds_create로 만들어라” 같은 규칙을 미리 등록)
모듈 로딩/부팅 초기화 때 커널이 공용 등록 함수(registration function) 를 호출해서,\- “이 자원 타입은 이런 상수 정보(예: family/type/path 등)로 식별되고”
- “실제로 만들 때는 이 create 함수를 써라”를 커널 내부 테이블에 등록해 둡니다
\
- 리소스 생성 함수는!! 리소스 객체를 나타내는 커널 구조체에 다른 상수와 가상 테이블을 할당한다.
나중에 실제로 자원을 만들 상황이 오면(예: socket() 호출), 커널이 ❶에서 등록해 둔 규칙을 보고 해당 create 함수를 호출합니다,
→ 내부 테이블에 등록해둔 것들을. - 리소스 생성 함수는 리소스 객체를 나타내는 커널 구조체에 다른 상수와 가상 테이블을 할당한다..
with GPT로 예시 들기.
create 함수 안에서는 “커널 구조체로 표현되는 자원 객체”를 세팅하면서 추가적인 상수들을 채우고 그 자원 타입에 맞는 ops/vtable을 구조체에 대입합니다.
→결과적으로 이후에 obj→ops→sendmsg( . . . ) 같은 간접호출이 발생하면 이 단계에서 붙여둔 vtable을 따라 해당 기능 코드로 제어 흐름이 가게 된다.
\
그래서.. 이 3가지 단계를 확인했는데. 이 확인한 걸로 뭐함?
- 일반적으로 사용되는 등록 함수 목록을 수동으로 수집.
- domain 지식을 활용하여 변수 이름을 기반으로 핵심 상수 값과 생성 함수를 추출.
- 등록 함수의 각 호출 사이트에 대해, 가상 테이블에 해당하는 리소스를 모델링하기 위해 추출된 모든 리소스 관련 상수를 사용.
→총 20개의 등록 함수와 소켓, 장치, 파일 시스템과 같은 일반 리소스와 관련된 핵심 변수를 수동으로 수집.
rds_rdma_extra_size를 트리거하는 것은 간접 호출을 포함, 이에 대한 제어 흐름은 rds_sendmsg로 지시한다.
→ 코드에 rds_sendmsg가 없는데 어떻게 제어 흐름을 지시?.
sendmsg 시스템 콜이 RDS 소켓에 대해 호출될 때, 커널은 간접 호출 메커니즘을 통해 rds_sendmsg를 호출하게 되고, 이 rds_sendmsg를 포함하는 호출 체인을 통해 최종적으로 rds_rdma_extra_size 함수에 도달하게 되는 것!!!!
이때, rds_sendmsg를 앵커 함수로 지정하고, 모든 참조를 검사한다.
→ rds_sendmsg는 RDS 소켓의 가상 테이블과 유사한 구조체인 rds_proto_ops에 속한다..
→ 그럼 rds_proto_ops는 어떤 리소스 관련 상수와 일치하는지 보기 위해서, 리소스 생성을 분섞!
→ RDS 소켓의 생성은 아래와 같다. 4행에서 소켓 등록 함수를 호출!! 그리고 인수와 재귀적으로 중첩된 필드를 분석하여 패밀리 유형과, 생성 함수를 얻는다!!!!!!
→ rds_create를 더 추적하여서 22행에서 rds_proto_ops의 할당을 얻는다.
→ 16행에서 소켓 유형에 대한 검사를 찾을 수 있으며, 이는 소켓 유형의 값이 할당문과 유사한 SOCK_SEQPACKET임을 나타낸다.
짜잔~ 그러면 rds_sendmsg가 AF_RDS_SOCK_SEQPACKET 리소스를 필요로 한다는 것을 앎.
진입 시스템 호출 식별
전통적인 제어 흐름 분석 + 매칭 접근 방식을 결합!!! 진입 시스템 호출 식별!
→ control flow analysis를 수행하여 제어 흐름 그래프를 구축, 대상에 도달할 수 있는 기본 시스템 호출을 식별!
→ CGF로 커널 함수 모델링 & 대상 함수를 시스템 호출 변형과 일치시킴!!!
→ 실행 불가능하면 죽여!
이거 그대로 복사 붙여넣기 하고 블로그에 사용할 이미지 만들어달라고 하니까 진짜 깔끔하게 정리해줬음..