- XZ Utils/liblzma 백도어는 2024년 3월 29일 Openwall OSS-security 메일링 리스트에서 공개됐으며, systemd 기반 배포판의 OpenSSH 서버 sshd에 원격 코드 실행 능력을 심는 것이 공격자의 최종 목표였을 가능성이 큼
- 감염 체인은 XZ 저장소의 테스트 파일과 빌드 인프라를 이용한 다단계 삽입으로 구성됐고,
build-to-host.m4,bad-3-corrupt_lzma2.xz,good-large_compressed.lzma를 거쳐 악성 오브젝트 파일이 빌드 중liblzma에 링크됨 - 백도어가 포함된 XZ 5.6.0과 5.6.1은 일부 주요 벤더의 베타·실험 빌드로 배포됐고, CVE-2024-3094는 심각도 10점으로 지정됨
- 바이너리 백도어는 GLIBC의 IFUNC와
cpuid호출 경로를 악용해 로딩되며, 이후 OpenSSL/libcrypto 관련 함수를 후킹해 감염된 머신의 연결을 감시하려 함 - 악성 코드는
/usr/bin/sshd실행 여부와 킬 스위치 환경변수를 확인하고, trie 기반 문자열 처리·동적 심볼 해석·rtdl-audit런타임 패치로 분석과 탐지를 어렵게 만듦
사건 개요와 영향 범위
- 2024년 3월 29일 Openwall OSS-security 메일링 리스트의 메시지를 통해 XZ 백도어 발견이 공개됨
- XZ는 여러 주요 Linux 배포판에 통합된 압축 유틸리티임
- 위험의 핵심은 백도어가 포함된
liblzma가 일부 systemd 기반 배포판에서 OpenSSH 서버 프로세스sshd와 연결될 수 있다는 점임- Ubuntu, Debian, RedHat/Fedora Linux는 OpenSSH가 systemd 기능을 사용하도록 패치되어 이 라이브러리에 의존함
- Arch Linux와 Gentoo는 영향받지 않는 것으로 정리됨
- 공격자의 최종 목표는 다른 사람이 사용할 수 없는 sshd 원격 코드 실행 기능을 넣는 것이었을 가능성이 큼
- 단일 악성 패치, 가짜 패키지, 타이포스쿼팅 패키지 중심의 다른 공급망 공격과 달리, 이 사건은 전 세계 SSH 서버 침해에 거의 성공할 뻔한 다단계 작전에 가까움
백도어가 들어간 방식
liblzma백도어는 빌드 과정과 테스트 파일을 함께 이용해 두 수준에서 삽입됨- 최종 패키지를 생성하는 빌드 인프라 소스 코드가
build-to-host.m4추가로 수정됨 - 테스트 케이스 파일 안에 숨겨진 스크립트와 바이너리 구성요소가 빌드 중 추출됨
- 최종 패키지를 생성하는 빌드 인프라 소스 코드가
- 감염 흐름은 세 파일을 중심으로 진행됨
build-to-host.m4: 다음 단계 스크립트를 추출하는 빌드 스크립트bad-3-corrupt_lzma2.xz: 셸 스크립트가 숨겨진 테스트 파일good-large_compressed.lzma: 악성 바이너리 오브젝트가 숨겨진 테스트 파일
- 추출된 악성 바이너리 구성요소는 컴파일 과정에서 정상 라이브러리와 링크되어 Linux 저장소로 전달될 수 있었음
- 주요 벤더는 이 악성 구성요소를 베타 및 실험 빌드에 포함해 배포함
- XZ Utils 침해에는 CVE-2024-3094가 할당됐고, 심각도는 최대치인 10점임
주요 타임라인
- 2024년 1월 19일: 새 메인테이너
jiaT75가 XZ 웹사이트를 GitHub Pages로 이전함 - 2024년 2월 15일:
build-to-host.m4가.gitignore에 추가됨 - 2024년 2월 23일: 악성 스크립트 단계가 들어 있는 두 “테스트 파일”이 추가됨
- 2024년 2월 24일: XZ 5.6.0이 릴리스됨
- 2024년 2월 26일:
CMakeLists.txt에 Landlock 보안 기능을 방해하는 커밋이 들어감 - 2024년 3월 4일: 백도어가 Valgrind 관련 문제를 일으킴
- 2024년 3월 9일: 두 테스트 파일이 갱신되고 CRC 함수가 수정됐으며 Valgrind 문제가 “수정”됨
- 2024년 3월 9일: XZ 5.6.1이 릴리스됨
- 2024년 3월 28일: 버그가 발견되고 Debian과 RedHat에 통보됨
- 2024년 3월 28일: Debian이 XZ 5.6.1을 5.4.5-0.2 버전으로 롤백함
- 2024년 3월 29일: OSS-security 메일링 리스트에 공개 메일이 게시됨
- 2024년 3월 29일: RedHat이 Fedora Rawhide와 Fedora Linux 40 beta에 백도어 포함 XZ가 배포됐다고 확인함
- 2024년 3월 30일: Debian이 빌드를 중단하고 재빌드 절차를 시작함
- 2024년 4월 2일: XZ 메인 개발자가 백도어 사건을 인정함
감염 체인의 3단계
-
1단계: 수정된
build-to-host.m4- XZ의 공식 GitHub 저장소 릴리스는 악성 테스트 파일을 포함했지만, 해당 파일들이 실행될 기회가 없어 처음에는 무해했음
- 감염을 부트스트랩하는 악성 코드는
jiaT75가 제어하던xz[.]tukaani.org에서 제공된 릴리스에 포함된 것으로 보임 - 대부분의 배포판은 이 URL을 사용했고, 다운로드된 릴리스에는 악성 코드가 들어 있는
build-to-host.m4파일이 포함됨 build-to-host.m4는 빌드 과정에서 실행되며bad-3-corrupt_lzma2.xz의 “깨진” 데이터를tr로 고친 뒤xz -d로 압축 해제함- 압축 해제 결과에는 나중에
/bin/bash로 실행될 셸 스크립트가 들어 있음
-
2단계: 삽입된 셸 스크립트
- 악성
.m4파일이 삽입한 스크립트는 Linux 머신에서 실행 중인지, 의도한 빌드 프로세스 안에서 실행 중인지 확인함 - 다음 단계 실행에는
good-large_compressed.lzma가 사용됨 - 이 파일은 XZ로 정상 압축되어 있지만, 압축 해제 데이터 안에 정크 데이터가 들어 있음
- 스크립트는
head파이프라인을eval로 실행해 데이터를 추출함 - 각
head명령은 다음 1024바이트를 무시하거나 다음 2048바이트 또는 724바이트를 추출함 - 총 33,492바이트가 추출됨
tail은 파일의 마지막 31,265바이트를 유지하고 나머지를 무시함- 이후
tr로 기본 치환을 적용해 난독화를 풀고, 두 번째 XZ 명령이 변환된 바이트를 rawlzma스트림으로 압축 해제한 뒤 결과를 셸로 전달함
- 악성
-
3단계: 백도어 오브젝트 추출
- 마지막 단계 셸 스크립트는 예상 환경에서 실행 중인지 여러 조건을 확인함
- 프로젝트가 IFUNC를 사용하도록 구성됐는지 확인함
- GCC로 컴파일되는지 확인함
- 이후 스크립트가 사용할 특정 파일이 프로젝트에 존재하는지 확인함
- 이 단계는
good-large_compressed.lzma의 다른 오프셋에 숨겨진 오브젝트 파일을 추출함 - 추출 절차는 여러 명령을 순차 실행하고, 각 명령의 출력을 다음 명령의 입력으로 넘김
- XZ 도구로
good-large_compressed.lzma를 추출함 eval $i와 여러head호출을 사용함- RC4와 유사한 커스텀 알고리듬으로 바이너리 데이터를 복호화함
- 복호화된 압축 파일을 다시 XZ로 추출함
- 앞부분 일부 바이트를 제거한 뒤 결과를
liblzma_la-crc64-fast.o로 저장함 - 마지막으로 스크립트는
liblzma의crc_x86_clmul.h파일에서is_arch_extension_supported함수를 수정함 __get_cpuid호출을_get_cpuid로 바꿔 밑줄 하나를 제거함- 이 변경으로 백도어 오브젝트가 라이브러리에 링크될 수 있음
바이너리 백도어 로딩 방식
- 원래 XZ 코드에는 CRC 계산용 특수 함수
lzma_crc32와lzma_crc64가 있음 - 두 함수는 ELF 심볼 테이블에서 GLIBC의 IFUNC 타입으로 저장됨
- IFUNC는 동적 링커가 공유 라이브러리를 로드할 때 사용할 함수를 동적으로 선택하게 함
- XZ는 최신 프로세서 기능인 CLMUL, SSSE3, SSE4.1을 확인한 뒤 최적화된 CRC 함수를 사용할지 결정하기 위해 이 기능을 사용함
- 프로세서 기능 확인에는
cpuid명령이 필요하고, GLIBC가 제공하는__get_cpuid래퍼가 호출됨 - 악성 스크립트가
__get_cpuid를_get_cpuid로 바꾸면서, 코드 호출은 백도어 오브젝트 파일 안의_get_cpuid심볼로 연결됨 - 초기 백도어 코드는
lzma_crc32와lzma_crc64가 같은 수정 함수_get_cpuid를 사용하기 때문에 두 번 호출됨- 단순 카운터로 이미 실행됐는지 확인함
lzma_crc64IFUNC가_get_cpuid를 호출하고 카운터 값이 1이면 최종 진입점으로 리다이렉션하는 단계가 시작됨
런타임 동작과 후킹 대상
- 백도어는 실행 중인 프로세스의 핵심 정보를 담는 구조체들을 초기화함
- 하드코딩된 오프셋으로 Global Offset Table(GOT) 주소를 찾고, 그 안의
cpuid포인터를 찾음 - 이후 포인터를 메인 악성 함수로 바꿔
cpuid를 호출하는 것처럼 악성 함수를 호출함 - 주 목표는 감염된 머신으로 들어오는 연결을 감시할 수 있도록 특정 함수를 후킹하는 것임
- 표적 함수는
libcrypto와 관련됨RSA_public_decrypt: 개인키로 서명된 암호문을 복호화할 때 사용됨EVP_PKEY_set1_RSA: 특정 컨텍스트의 RSA 키를 설정할 때 사용됨- 최신 SSH 서버 버전에는 없지만 독립 SSH 키 생성 도구에서 사용됨
RSA_get0_key: RSA 키를 가져올 때 사용됨
- 후킹 과정은 여러 내부 구조체 접근과 프로세스별 구조체 조작이 필요하며, 악성 코드는 분석 회피를 위한 검사를 함께 수행함
실행 환경 검사와 분석 회피 기법
-
sshd와 킬 스위치 확인- 백도어는 먼저 심볼 주소를 동적으로 해석하는
ld-linux동적 링커를 파싱함 - 현재 실행 환경이 기준에 맞는지 확인함
- 현재 프로세스가
/usr/bin/sshd인지 확인함 - 킬 스위치가 존재하는지 확인함
- 현재 프로세스 이름은
argv[0]에서 추출함 - 프로세스 환경변수를 모두 파싱해 내부 문자열 테이블에 있는 값과 일치하는지 확인함
- 일치하는 환경변수가 있거나 프로세스가
sshd가 아니면 악성 코드는 실행을 중단함 - 내부 테이블의 유일한 변수
yolAbejyiejuvnup=Evjtgvsh5okmkAvj는 이 맥락에서 킬 스위치로 동작함
- 백도어는 먼저 심볼 주소를 동적으로 해석하는
-
trie 기반 문자열 처리
- 백도어의 특징 중 하나는 문자열 작업에 단일 trie 구조를 사용한다는 점임
- 함수 이름 같은 상수를 직접 비교하거나 문자열 해시로 비교하는 대신, trie 조회 결과가 특정 상수값과 같은지 확인함
- ELF 헤더의 매직값은 trie에서 0x300을 반환함
system함수 이름은 0x9F8 반환값과 매칭됨- trie는 비교뿐 아니라 호스트 바이너리 안의 문자열을 찾는 데도 사용됨
- 예시로
ssh-2.0같은 문자열 포인터를 사용하는 함수는 백도어 본문에 의심스러운 데이터가 남지 않도록 호스트 바이너리에서 문자열을 찾음 - 구현은 16바이트 비트마스크를 사용함
- 각 절반은 입력 바이트 범위
0x00-0x3F,0x40-0x7F에 대응함 - trie 리프 노드는 2바이트이며, 3비트는 플래그로 쓰이고 나머지는 값 또는 다음 노드 위치로 예약됨
심볼 해석과 rtdl-audit 후킹
-
심볼 해석 루틴
- 백도어에는 ELF Symbol 구조를 찾기 위한 심볼 해석 루틴이 최소 3개 있음
- 모든 심볼 해석 함수는 trie에서 검색할 키를 입력으로 받음
- 한 해석 함수는 모든 심볼을 순회하며 원하는 키를 가진 심볼을 찾고, 발견되면
Elf64_Sym구조를 반환함 - 이 구조는 이후 백도어 내부 구조체를 채워 필요한 함수 포인터들을 보관하는 데 사용됨
- 이 과정은 Windows 위협에서 흔히 보이는 API 해싱 루틴과 유사함
- 백도어는 이후 암호화 루틴에서 사용할 OpenSSL
libcrypto함수들을 다수 검색함 - 찾고 해석한 함수 수를 추적하며, 이 값으로 정상 실행 여부와 중단 여부를 결정함
-
lzma_alloc를 악용한 가짜 allocator- 또 다른 심볼 해석기는
liblzma자체 함수인lzma_alloc를 악용함 lzma_alloc는 기본 allocator인malloc또는 커스텀 allocator를 사용해 메모리를 효율적으로 할당하도록 돕는 함수임- XZ 백도어에서는 가짜 allocator를 통해 이 함수가 또 다른 심볼 해석기처럼 동작함
- 원래 “할당 크기”로 쓰이는 매개변수는 실제로 trie 내부의 심볼 키로 사용됨
- 이 트릭은 백도어 분석을 어렵게 만들기 위한 목적임
- 백도어는 실행 중 심볼을 동적으로 해석하며, 모든 심볼을 한 번에 해석하거나 필요한 순간에만 해석하는 방식으로 제한되지 않음
- 해석 대상에는 정상 OpenSSL 함수부터 머신에서 명령을 실행하는
system같은 함수까지 포함됨
- 또 다른 심볼 해석기는
-
rtdl-audit런타임 패치- 백도어는 함수 후킹을 위해 동적 링커의 rtdl-audit 기능을 사용함
- 일반적으로
rtdl-audit는 개발자가 커스텀 공유 라이브러리를 만들어 심볼 해석 같은 링커 이벤트를 통지받는 기능임 - XZ 백도어는 별도 공유 라이브러리를 만드는 대신, 메모리에 이미 등록된 기본 인터페이스를 런타임 패치해 심볼 해석 루틴을 탈취함
- 동적 링커 메모리 영역의
dl_audit전역 변수에 저장된 악성audit_iface구조체에는symbind64콜백 주소가 들어 있음 - 동적 링커가 이 콜백을 호출하면 모든 심볼 정보가 백도어 제어로 넘어가고, 표적 함수에 대해 악성 주소를 반환해 후킹이 달성됨
dl_audit와dl_naudit주소는dl_main과dl_audit_symbind_alt함수를 디스어셈블해 얻음- 백도어에는 명령어 디코딩을 위한 내부의 최소형 디스어셈블러가 포함되어 있으며,
*audit주소 같은 특정 값을 찾을 때 폭넓게 사용됨
확인된 백도어 배포본과 탐지 정보
-
백도어 포함 소스 배포본
- xz-5.6.0
- MD5:
c518d573a716b2b2bc2413e6c9b5dbde - SHA1:
e7bbec6f99b6b06c46420d4b6e5b6daa86948d3b - SHA256:
0f5c81f14171b74fcc9777d302304d964e63ffc2d7b634ef023a7249d9b5d875
- MD5:
- xz-5.6.1
- MD5:
5aeddab53ee2cbd694f901a080f84bf1 - SHA1:
675fd58f48dba5eceaf8bfc259d0ea1aab7ad0a7 - SHA256:
2398f4a8e53345325f44bdd9f0cc7401bd9025d736c6d43b372f4dea77bf75b8
- MD5:
- xz-5.6.0
-
분석된 주요 아티팩트
bad-3-corrupt_lzma2.xz:86fc2c94f8fa3938e3261d0b9eb4836be289f8aebuild-to-host.m4:b4dd2661a7c69e85f19216a6dbbb1664good-large_compressed.lzma:540c665dfcd4e5cfba5b72b4787fec4fliblzma_la-crc64-fast.o:212ffa0b24bb7d749532425a46764433
-
알려진 백도어 포함 라이브러리
- Debian Sid
liblzma.so.5.6.0- MD5:
4f0cf1d2a2d44b75079b3ea5ed28fe54 - SHA1:
72e8163734d586b6360b24167a3aff2a3c961efb - SHA256:
319feb5a9cddd81955d915b5632b4a5f8f9080281fb46e2f6d69d53f693c23ae
- MD5:
- Debian Sid
liblzma.so.5.6.1- MD5:
53d82bb511b71a5d4794cf2d8a2072c1 - SHA1:
8a75968834fc11ba774d7bbdc566d272ff45476c - SHA256:
605861f833fc181c7cdcabd5577ddb8989bea332648a8f498b4eef89b8f85ad4
- MD5:
- Debian Sid
-
탐지명
- Kaspersky 제품은 공격 관련 악성 객체를 HEUR:Trojan.Script.XZ 및 Trojan.Shell.XZ로 탐지함
- Kaspersky Endpoint Security for Linux는 Critical Areas Scan 작업의 일부로 SSHD 프로세스 메모리 안의 악성 코드를 MEM:Trojan.Linux.XZ로 탐지함
- 제공된 Yara 규칙은 CVE-2024-3094와 관련된 악성
get_cpuid함수를 찾기 위한liblzma_get_cpuid_function규칙임