여담
Dirty Frag의 RxRPC variant는 ESP와 같은 page-cache write class지만 sink가 다르다. authencesn의 4-byte scratch STORE 대신, RXKAD가 packet 앞 8 bytes를 pcbc(fcrypt)로 in-place decrypt하면서 page를 직접 덮는다.
기존 System Hacking 글에 RxRPC가 없으므로 이 글에서는 protocol과 security negotiation부터 취약한 decrypt path까지 새로 정리한다.
RxRPC란
RxRPC는 UDP 위에 reliable call/response abstraction을 제공하는 protocol이다. Linux에서는 AF_RXRPC socket family로 노출되며 AFS가 대표 사용자다.
Application / AFS
-> RxRPC call
-> UDP transport
-> IP
Plain Text
복사
connection 안에 여러 call이 존재하고, 각 call은 request phase와 reply phase로 진행된다. security가 필요하면 server가 CHALLENGE를 보내고 client가 RESPONSE를 돌려준 뒤 이후 DATA packet을 보호한다.
RXKAD와 security level
RXKAD는 RxRPC에서 사용하는 security class 중 하나다. 공개 PoC는 securityIndex = 2와 RXRPC_SECURITY_AUTH level을 사용한다.
level 1 AUTH에서는 packet payload 전체를 암호화하는 대신 앞쪽 8-byte security header를 decrypt해서 packet length와 checksum을 검증한다.
RXRPC DATA payload
[ encrypted security header: 8 bytes ][ remaining payload ]
|
v
rxkad_verify_packet_1()
Plain Text
복사
cipher는 pcbc(fcrypt)다. fcrypt는 AFS용 64-bit block cipher이고 RXKAD session key는 Linux key retention API의 rxrpc key payload에서 가져온다.
공격자가 key를 제어하는 방법
unprivileged process도 자신의 process keyring에 RxRPC v1 token을 등록할 수 있다.
add_key("rxrpc", description, token, token_len,
KEY_SPEC_PROCESS_KEYRING);
C
복사
token의 session_key가 connection의 pcbc(fcrypt) key K가 된다. 이 variant는 XFRM SA를 등록하지 않으므로 CAP_NET_ADMIN이나 새 user namespace가 필요하지 않다.
PoC는 로컬 fake UDP server와 AF_RXRPC client를 함께 만들고 CHALLENGE/RESPONSE handshake를 흉내 내어, client kernel이 attacker key K로 security context를 만들게 한다.
취약한 in-place decrypt
rxkad_verify_packet_1()은 skb payload의 첫 8 bytes를 SGL로 만들고 같은 SGL을 src와 dst로 사용한다.
sg_init_table(sg, ARRAY_SIZE(sg));
ret = skb_to_sgvec(skb, sg, sp->offset, 8);
memset(&iv, 0, sizeof(iv));
skcipher_request_set_crypt(req, sg, sg, 8, iv.x);
crypto_skcipher_decrypt(req);
C
복사
정상 skb라면 packet page 안에서 decrypt되는 것뿐이다. 하지만 sp->offset 위치가 splice로 심은 file page-cache frag라면 다음 identity가 성립한다.
target file page P
== skb frag page
== sg page
== decrypt src
== decrypt dst
Plain Text
복사
따라서 single-block decrypt 결과 8 bytes가 page P에 직접 STORE된다.
왜 8-byte arbitrary write가 바로 나오지는 않는가
ESP variant는 seq_hi를 통해 4 bytes를 직접 선택한다. RxRPC variant는 write value가 다음 함수 결과다.
P = pcbc_decrypt(C, K, IV=0)
single block이므로
P = fcrypt_decrypt(C, K)
Plain Text
복사
attacker가 제어하는 것은 key K이고 ciphertext C는 target file의 현재 8 bytes다. 그래서 공개 PoC는 userspace에 fcrypt를 옮겨 원하는 plaintext pattern이 나올 때까지 K를 brute force한다.
또한 overlapping 8-byte write를 사용하면 앞선 write가 다음 ciphertext를 바꾼다. 따라서 각 단계의 K를 찾을 때는 원본 file byte가 아니라 직전 write가 반영된 실제 C를 입력으로 사용해야 한다.
공개 PoC가 /etc/passwd를 고르는 이유
공개 PoC는 /etc/passwd 첫 줄의 offset 4, 6, 8에 겹치는 8-byte STORE 세 번을 수행해 다음 형태를 만든다.
before: root:x:0:0:root:/root:/bin/bash
after : root::0:0:GGGGGG:...
Plain Text
복사
핵심은 root account의 password field를 비우는 것이다. PAM 설정이 nullok를 허용하는 환경에서는 su -가 빈 password를 받아들여 root shell로 이어질 수 있다.
이 부분은 distribution PAM policy에 의존한다. page-cache write primitive와 최종 LPE strategy는 구분해서 봐야 한다.
trigger call path
attacker-controlled UDP packet
-> RxRPC input
-> rxrpc_input_call_event()
-> security ops
-> rxkad_verify_packet()
-> rxkad_verify_packet_1()
-> skb_to_sgvec()
-> skcipher_request_set_crypt(req, sg, sg, 8, IV=0)
-> crypto_skcipher_decrypt()
-> page-cache page P에 8-byte STORE
Plain Text
복사
security header 검증이 나중에 실패해도 decrypt STORE 자체는 이미 완료되어 있다.
ESP variant와 상호 보완되는 이유
•
ESP: esp4/esp6는 많은 배포판에 있지만 user namespace와 namespace 내부 CAP_NET_ADMIN이 필요하다.
•
RxRPC: user namespace가 필요 없지만 rxrpc.ko가 설치·로드 가능한 환경이어야 한다.
Ubuntu 계열은 unprivileged user namespace가 AppArmor로 제한되는 경우가 있지만 RxRPC module을 제공하는 경우가 있어 RxRPC path가 빈틈을 메운다. 다른 배포판은 RxRPC module이 없더라도 ESP path가 열릴 수 있다.
정리
RxRPC variant는 shared skb frag를 RXKAD가 private packet buffer로 오판한 상태에서 pcbc(fcrypt) single-block decrypt를 in-place로 수행해 8-byte page-cache STORE를 만든다. write value는 key brute force로 조정하며, user namespace 없이 도달할 수 있다는 점이 ESP variant와 가장 큰 차이다.