Go to Top

Go to Top

위협 인텔리전스

위협 인텔리전스

위협 인텔리전스

APT Down - The North Korea Files

APT Down - The North Korea Files

APT Down - The North Korea Files

엔키화이트햇

엔키화이트햇

2025. 9. 22.

2025. 9. 22.

2025. 9. 22.

Content

Content

Content

개요

본 보고서는 Phrack Magazine에서 공개된 APT Down–The North Korea Files 자료를 토대로 추가 분석한 내용들을 다룬다. 공격자가 실제로 사용한 VMware VM, VPS 덤프 파일도 공개되어 공격자의 활동 내역을 심층 분석할 수 있었고, 이로 인해 공격 배후도 유추할 수 있었다.

특히 공개된 파일 중에 루트킷 소스코드가 존재하는데, 이는 당사가 2022년 금융회사 침해사고 조사 과정에서 확인한 루트킷의 소스코드인 것으로 판명되었다. 코드 로직은 물론 사용된 암호화 키까지 일치하였다. 또한 2022년 침해사고 조사 당시 확인된 루트킷의 2025년 최신 버전 소스코드가 추가로 확인되었다.

이 외에도 Ivanti 1-Day 익스플로잇, 유출된 것으로 추정되는 외교부 홈페이지 소스코드, GPKISecureWebX 소스코드 그리고 검찰, 방첩사령부 등을 대상으로 한 피싱 공격도 확인되었다. 이러한 정황은 공격자가 과거부터 현재까지 한국을 주요 표적으로 삼아 지속적으로 활발한 공격 활동을 수행해왔고, 일반적으로 외부에서 쉽게 탈취하기 어려운 자료가 유출된 점, 루트킷 및 1-Day 익스플로잇을 활용한다는 점에서 공격자는 높은 수준의 공격 역량을 보유하고 있으며, 내부망까지 침투하여 민감 자료를 탈취했음을 시사한다.

분석 파일 개요

본 장에서는 분석 내용을 설명하기에 앞서, APT Down – The North Korea Files 보고서와 함께 공개된 파일의 구성을 간략히 설명한다.

caption - .onion 사이트에 게시된 공개 파일 목록

주요 파일은 work.zip, vps.zip, file-lists_and_misc.zip으로 설명은 아래와 같다.

work.zip

  • 공격자 VMware VM(Deepin 20.9) 덤프 파일로, 실제 사용된 워크스테이션 환경이 포함되어 있으며 악성코드 소스코드, 공격 도구, 탈취된 자료, 각종 로그 등이 존재한다.

  • 호스트 C:\가 hgfs로 마운트된 상태로, 공격자 호스트에 존재하는 파일도 확인할 수 있다.

vps.zip

  • 공격자 소유 VPS 덤프 파일로, 스피어 피싱 공격에 사용된 이력과 각종 로그가 존재한다.

  • 국방방첩사령부 ,검찰 등 여러 기관을 타겟으로 한 스피어 피싱 공격을 확인할 수 있다.

file-lists_and_misc.zip

  • 공격자 워크스테이션의 구글 타임라인, 명령어 히스토리, 전체 파일 목록 등이 존재한다.

  • 전체 파일 목록에서 덤프 파일에는 존재하지 않는 경로나 파일을 확인할 수 있다.

1. 악성코드 및 공격 분석

본 장에서는 공격자 VMware VM 덤프 파일에서 확인된 악성코드와 VPS 덤프 파일에서 확인된 스피어 피싱 공격 및 인프라 운용 정황에 대한 내용을 다룬다. 특히 tomcat20220420_rootkit, tomcat20250414_rootkit_linux234 디렉토리에서 확인된 루트킷과 백도어는 과거 당사가 조사한 국내 금융회사 침해사고와 관련있다.

1.1. tomcat20220420_rootkit

1.1.1. Backdoor

work/home/user/Desktop/tomcat20220420_rootkit/tomcat20220420_rootkit/work 디렉토리 내에 백도어 소스코드가 존재한다. 백도어 주요 행위는 명령코드에 따라 악성 행위를 하거나 프록시 역할로 통신을 중계한다. 백도어는 단독으로 실행되지 않고, 항상 루트킷에 의해 실행된다. 백도어 실행 인자 정보는 아래 표와 같다.

| 번째 인자  | 인자 개수 1개일  | 인자 개수 2개일  |
| --- | --- | --- |
| cb | 하드코딩된 아이피, 포트 사용 | 2번째 인자 값을 포트로 사용, 하드코딩된 아이피 사용 |
| proxy | 프록시 플래그 활성화 | X |
| cb, proxy 이외의  | 인자 값을 포트로 사용 | X

프록시 플래그가 활성화되어 있으면 명령코드 관련 로직은 실행되지 않고, AES-CBC-256으로 암호된 데이터(__step2__)를 수신할 때까지 대기한다.

master.c 데이터 검사 로직

caption - master.c 데이터 검사 로직

AES key와 iv는 아래와 같다.

  • AES key: 603deb15153a715e2b73aef3857d758b1f552c573e6158d72d9811a33914defe

  • iv: 603deb15153a715e2b73aef3857d758b1f552c573e6158d72d9811a33914defe

백도어는 정상 통신으로 위장하기 위해 클라이언트와의 소켓 통신을 아래 프로토콜 중 하나로 설정한다.

  • HTTP

  • HTTPS

  • SSL

  • TCP

  • SMTP

실제로 사용하는 프로토콜은 install_common.h에 정의되어 있다.

install_common.h

caption - install_common.h

명령코드를 포함한 모든 송수신 데이터는 xor로 암·복호화된다. 이때 사용되는 키는 "1101link"인데, 암·복호화 로직을 보면 단일 바이트와 한 번 xor한 것과 동일하다. 따라서 "1101link"와 xor 한 결과는, 1과 xor한 것과 동일하다.

encrypt.c EncodeDecode 함수

caption - encrypt.c EncodeDecode 함수

명령코드에 따른 행위는 아래 표와 같다.

| 명령코드 이름 | 명령코드 | 행위 | 응답코드 이름 | 응답코드 |
| --- | --- | --- | --- | --- |
| CMD_LOGIN | 0x11 | 응답코드를 클라이언트로 전송한다. | CMD_LOGIN_YES | 0x12 |
| CMD_FILE_UP | 0x15 | 클라이언트에서 전송하는 파일을 저장한다. | CMD_UP_YES | 0x16 |
| CMD_FILE_DOWN | 0x18 | 지정된 파일을 클라이언트로 전송한다. | CMD_DOWN_YES | 0x19 |
| CMD_SHELL | 0x1b | 주어진 명령어를 실행한 결과를 클라이언트로 전송한다. | CMD_SHELL_YES | 0x1c |
| CMD_TRANSFER | 0x1f | 클라이언트에게 접속 정보를 전달 받은 새로운 백도어에 패스워드를 전송하여 실행 중인 백도어를 종료하도록 하고 이후 클라이언트에서 전송하는 패킷을 새로운 백도어로 전송하는 프록시 서버로서 동작한다. | CMD_TRANSFER_YES | 0x20 |
| CMD_PROXY_TRANSFER | 0x21 | 클라이언트에게 접속 정보를 전달 받은 새로운 백도어에 AES로 암호화한 패스워드, "__step2__", "__step3__"차례로 전송하여 새로운 백도어가 프록시 서버로서 동작하도록 한다. 이후 클라이언트에서 전송하는 패킷을 새로운 백도어로 전송하는 프록시 서버로서 동작한다. | CMD_PROXY_TRANSFER_YES | 0x22 |
| CMD_SOCKS_PROXY_TRANSFER | 0x23 | 클라이언트에게 접속 정보를 전달 받은 새로운 백도어에 AES로 암호화한 패스워드, "__step2__", "__step3__"차례로 전송하여 새로운 백도어가 프록시 서버로서 동작하도록 한다. 이후 클라이언트에서 전송하는 패킷을 새로운 백도어로 전송하는 프록시 서버로서 동작한다. 이때 SOCKS5 프록시 연결을 수행한다. | CMD_SOCKS_PROXY_TRANSFER_YES | 0x24 |
| CMD_BACK | 0x13 | 응답코드를 클라이언트로 전송한다. | CMD_BACK_YES | 0x14 |
| CMD_SINGLE_CMD | 0x25 | 주어진 명령어를 실행한 결과를 클라이언트로 전송한다. 이후 백도어 프로세스를 종료한다. | CMD_SINGLE_CMD_YES | 0x26 |
| CMD_IN_SINGLE_CMD | 0x27 | 주어진 명령어를 실행한 결과를 클라이언트로 전송한다. | CMD_IN_SINGLE_CMD_YES | 0x28 |
| CMD_D_SINGLE_CMD | 0x2b | 주어진 명령어를 독립된 프로세스로 실행한다. | CMD_D_SINGLE_CMD_YES | 0x2c |
| CMD_D_OUT_SINGLE_CMD | 0x2d | 주어진 명령어를 독립된 프로세스로 실행한다. 이후 백도어 프로세스를 종료한다. | CMD_D_OUT_SINGLE_CMD_YES | 0x2e

1.1.2. syslogk rootkit

work/home/user/Desktop/tomcat20220420_rootkit/tomcat20220420_rootkit/main.c 파일은 syslogk로 명명된 루트킷 소스코드이다. 루트킷은 백도어 및 관련 악성코드가 위치한 디렉터리를 은닉하여 일반적으로 탐지되지 않도록 한다. 또한 매직 패킷 수신 시에만 백도어를 실행하여, 공격자가 원하는 시점에만 백도어가 동작한다.

루트킷은 3개의 함수를 후킹해 프로세스, 포트, 디렉토리를 은닉한다. 후킹 관련 정보는 아래 표와 같다.

| 후킹 대상 함수 | 후킹 함수 | 행위 |
| --- | --- | --- |
| proc_roo_readdir | hk_proc_readdir | 프로세스 은닉 |
| tcp4_seq_show | hk_t4_seq_show | 열린 포트 은닉 |
| readdir | hk_root_readdir | 디렉토리 은닉

후킹 함수 내부에서는 사전에 정의된 프로세스 이름, 경로, 포트 문자열과 대상 엔트리를 strstr 함수로 비교해, 일치할 경우 해당 엔트리를 반환하기 전에 제거하는 방식으로 은닉을 구현하였다.

이후 Netfilter 관련 nf_register_hook 함수를 사용해 훅 지점에 콜백 함수를 등록한다. nf_reagister_hook에 의해 콜백 함수가 등록되는 두 개의 훅 지점에 대한 설명은 아래와 같다.

  • NF_INET_LOCAL_IN : 로컬 호스트로 패킷을 보내기 전

  • NF_INET_LOCAL_OUT : 로컬 호스트에서 패킷을 외부로 보내기 전

hkcap.c 후킹 함수 등록 로직

caption - hkcap.c 후킹 함수 등록 로직

위 방법으로 루트킷은 로컬 호스트에서 오고가는 모든 패킷을 감시하고, 공격자가 보낸 매직 패킷을 확인하면 악성 행위를 한다.

NF_INET_LOCAL_IN 지점 콜백 함수는 수신한 패킷 프로토콜이 TCP이고, 페이로드를 복호화해 특정 조건들과 일치하는지 확인한다. 복호화에 사용되는 데이터는 아래 표와 같다.

| 정보 |  |
| --- | --- |
| AES key | "603deb15153a715e2b73aef3857d758b1f552c573e6158d72d9811a33914defe" |
| iv | "12a3bb47535ec0d53953a6fbad43f573" |
| xor key | "1101link"

복호화 이후 검증하는 조건 목록과 처리 동작은 아래 표와 같다.

| 조건  | 행위 | 콜백 함수 |
| --- | --- | --- |
| 1. xor로 복호화된 페이로드 4바이트가 "44332211"(16진수)이다.-n2. 이후 4바이트가 "ssecuremw"이다. | /bin/sh -c 통해 명령어 실행 | nfinpro |
| 1. syn 패킷이다.-n2. 패킷 window 값이 1022이고 id, seq 값이 common.c id_list, seq_list에 존재한다. | /bin/sh -c 통해 백도어를 실행하고, 패킷 페이로드 전송 | nfin, nfinpro |
| 1. 백도어와 통신하는 ip에 대한 패킷이다.-n2. 패킷 페이로드가 "ssecuremw"이다. | 현재 실행중인 백도어 프로세스 종료 | nfin, nfinpro |
| 1. 백도어와 통신하는 ip에 대한 패킷이다.-n2. 패킷 페이로드가 "ssecuremw"아니다. | 패킷의 dest 값을 현재 실행중인 백도어 통신 포트로 변경 | nfin, nfinpro |
| 1. 데이터 4바이트가 0x0000002c이다.-n2. 오프셋 10부터 AES로 암호화된 패스워드(ssecuremw)존재한다. | /bin/sh -c 통해 proxy 인자를 설정하여 백도어 실행 | nfinpro |
| 1. 데이터가 AES로 암호화된 패스워드(ssecuremw)시작한다. | /bin/sh -c 통해 proxy 인자를 설정하여 백도어 실행 | nfinpro |
| 1. 데이터가 AES로 암호화된 데이터(__step3__)시작한다. | 현재 실행중인 백도어와 통신하는 ip를 해당 패킷의 ip로 변경 | nfinpro

NF_INET_LOCAL_OUT 지점에서 콜백 함수는, 백도어가 보내는 패킷의 source를 백도어가 통신하지 않는 포트로 변경한다.

Netfilter 콜백 함수 등록을 마친 후, 루트킷은 모듈 리스트에서 자기 자신을 제거해 탐지를 어렵게 한다. 이로 인해 lsmod 명령어를 입력해도 루트킷을 찾을 수 없다.

hide_module 함수

caption - hide_module 함수

모듈 리스트에서 제거된 루트킷에 install.h의 MAGIC_DRBIN 값을 작성하면 lsmod 명령어로 다시 확인할 수 있다.

proc_write 함수 로직

caption - proc_write 함수 로직

1.1.3. Backdoor Client

work/home/user/Desktop/tomcat20220420_rootkit/tomcat20220420_rootkit/work/tcat.c 파일은 백도어 클라이언트 소스코드이다. 이 파일을 공격자가 C&C 서버에서 실행한 것으로 보인다.

실행 시 여러 옵션을 지정할 수 있다. 전체 옵션은 아래 표 참고 바란다.

| Long Option | Short Option | 의미 |
| --- | --- | --- |
| HOST | H | 백도어 주소 |
| PORT | P | 백도어 포트 |
| password | p | 백도어 접속 시에 사용할 패스워드 (기본: ssecuremw) |
| callback | c | callback 모드 활성화 |
| single_command | s | 주어진 명령어를 실행하도록 전송 |
| daemon_command | d | 주어진 명령어를 새로운 프로세스에서 실행 하도록 전송 (백그라운드 실행) |
| proxy | x | 프록시 서버 사용 |
| socks_proxy | y | socks5 프록시 서버 사용 |
| socks_aim_hostname | i | 사용할 socks5 프록시 서버의 주소 |
| socks_aim_port | o | 사용할 socks5 프록시 서버의 포트 |
| socks_username | u | socks5 프록시 서버에 로그인할 유저이름 |
| socks_password | a | socks5 프록시 서버에 로그인할 패스워드 |
| knock_protocol | k | 백도어 초기 접속 시도에 사용할 프로토콜 (0: TCP, 1: HTTP, 2: SSH) |
| ethernet_interface | e | 사용할 이더넷 인터페이스 |
| cookie | 5 | HTTP 혹은 HTTPS를 사용할 때의 쿠키  |
| host | 6 | HTTP 혹은 HTTPS를 사용할 때의 selfdefined host  |
| main_protocol | m | 백도어 통신에 사용할 프로토콜 (0: TCP, 1: HTTP, 99: old HTTP) |
| kc | 1 | 주어진 명령어를 커널 모듈에서 출력 없이 실행하도록 전송

옵션에 따라 백도어에 명령어를 전달하는 방식은 아래와 같이 4가지로 나누어진다.

  • single cmd : 입력받은 명령어를 백도어에서 실행하도록 전송한다.

  • single daemon cmd : 입력받은 명령어를 백도어에서 새로운 프로세스를 생성하여 실행하도록 전송한다.

  • input loop : exit을 입력받을 때까지 무한히 명령어를 입력받아 명령어에 따른 행위를 수행한다.

  • kernel cmd : 입력받은 명령어를 커널 모듈에서 출력 없이 실행하도록 전송한다.

input loop를 제외한 모든 명령어 전달 방식은 전송 이후 클라이언트가 종료된다. input loop에서 명령어에 따라 수행하는 행위는 아래 표와 같다.

| 명령어 | 행위 |
| --- | --- |
| shell | exit을 입력 받을 때까지 shell 명령어를 입력 받아 백도어에서 실행하도록 전송한다. |
| trans | CMD_TRANSFER 명령어를 보내 현재 연결된 백도어를 프록시로 사용하여 새로운 백도어에 연결한다. |
| upload | 입력받은 경로의 단일 파일을 백도어에 업로드한다. |
| download | 입력받은 경로의 단일 파일을 백도어에서 다운로드한다. |
| back | 현재 백도어 연결을 종료하고 프록시로 사용하던 백도어에 접속한다. |
| exit | 프로세스를 종료한다. |
| proxy_trans | CMD_PROXY_TRANSFER 명령어를 보내 현재 연결된 백도어를 프록시로 사용하여 새로운 백도어에 연결한다. |
| socks_trans | CMD_SOCKS_PROXY_TRANSFER 명령어를 보내 현재 연결된 백도어를 SOCKS5 프록시로 사용하여 새로운 백도어에 연결한다. |
| old_trans | trans 명령어와 같은 행위를 수행하지만 통신 프로토콜을 설정하지 않는다. |
| old_proxy_trans | proxy_trans 명령어와 같은 행위를 수행하지만 통신 프로토콜을 설정하지 않는다. |
| old_socks_trans | socks_trans 명령어와 같은 행위를 수행하지만 통신 프로토콜을 설정하지 않는다. |
| sh | 입력받은 명령어를 백도어에서 실행하도록 전송한다. |
| dsh | 입력받은 명령어를 백도어에서 새로운 프로세스를 생성하여 실행하도록 전송한다. (백그라운드 실행) |
| cookie | HTTP 통신에 사용하는 쿠키 값을 설정하거나 현재 쿠키값을 출력한다. |
| host | 현재 연결된 백도어 정보를 출력한다

모든 통신은 xor key(1101link)로 암호화되어 이루어지고, common.h 파일에 정의된 GENERAL_MODULE, GENERAL_PROTOCOL 값을 패스워드와 함께 sha512로 해싱하여 항상 메시지의 맨 앞에 붙여 전송한다. 백도어와의 통신은 옵션에 따라 아래 프로토콜 중 하나로 설정된다.

  • TCP

  • HTTP

  • OLD HTTP - OLD HTTP는 일반적인 HTTP와 달리 쿠키 값을 설정하지 않는다.

1.2. tomcat20250414_rootkit_linux234

1.2.1. Backdoor

work/mnt/hgfs/Desktop/tomcat20250414_rootkit_linux234/tomcat20250414_rootkit_linux2345/work 디렉토리 내에 2022년 버전의 백도어와 동일한 2025년 버전의 백도어 소스코드가 존재한다. 2022년 버전 백도어의 업그레이드 버전으로, callback 딜레이 통신 시 패스워드를 확인하며 시간을 지정하거나 파일 다운로드, 업로드 속도를 제한하는 등의 기능이 추가되었다. 전체 옵션은 아래 표와 같다.

| Long Option | Short Option | 행위 |
| --- | --- | --- |
| module | m | callback 모드 지정 |
| protocol | p | 프로토콜 지정 |
| port | P | 포트 지정 |
| HOST | H | callback 프록시로 사용할 주소 지정 |
| ft | f | callback 딜레이 시간 지정 |
| tt | t | 딜레이를 제외한 callback 딜레이 시간 지정 |
| LL | L | 로그파일 경로 지정

패스워드 외에 마스터 패스워드가 추가되었는데, 지정 포트로 소켓 서버를 생성하고, 클라이언트 연결 시 마스터 패스워드와 통신 설정 정보를 sha512로 해싱한다. 마스터 패스워드는 common.h에 정의된 !@nf4@#fndskgadnsewngaldfkl을 사용한다. 해시 값은 전역 변수에 저장되고, ssl 통신 시 통신 설정 정보가 올바른지 확인하지만 실제로 사용되지 않는다.
이후 패스워드를 마스터 패스워드 확인 로직과 동일하게 통신 설정 정보와 sha512 해싱하여 동일한지 확인한다.

encrypt.c passcheck_check 함수

caption - encrypt.c passcheck_check 함수

패스워드는 Miu2jACgXeDsxd로, 패스워드를 포함한 설정 정보는 config.sh 파일에서 원하는 값으로 변경하면 빌드할 때 적용된다.

config.sh 기본 설정 정보

caption - config.sh 기본 설정 정보

통신에 사용되는 프로토콜 목록은 2022 백도어와 동일하다.

  • HTTP

  • HTTPS

  • SSL

  • TCP

  • SMTP

데이터를 암·복호화하는 xor key도 동일하지만 5개의 xor key와 암·복호화 함수가 추가되었다.

encrypt.c에서 확인한 전체 xor key

caption - encrypt.c에서 확인한 전체 xor key

2022 백도어에 비해 여러 행위가 추가되었고, 패킷 조건이 바뀌며 TRANSFER이 붙은 명령코드 행위가 변경되었다. 새로 추가된 명령코드는 아래와 같다.

  • CMD_NEW_UPLOAD

  • CMD_NEW_DOWNLOAD

  • CMD_LISTEN_PROXY_TRANS

  • CMD_TRANSFER

  • CMD_PROXY_TRANSFER

  • CMD_SOCKS_PROXY_TRANSFER

  • CMD_SOCKS_PROXY

  • CMD_NEW_SINGLE_CMD

명령코드에 따른 행위는 아래 표와 같다.

| 명령코드 이름 | 명령코드 | 행위 | 응답코드 이름 | 응답코드 |
| --- | --- | --- | --- | --- |
| CMD_LOGIN | 0x11 | 응답코드를 클라이언트로 전송한다. | CMD_LOGIN_YES | 0x12 |
| CMD_FILE_UP | 0x15 | 클라이언트에서 전송하는 파일을 저장한다. | CMD_UP_YES | 0x16 |
| CMD_FILE_DOWN | 0x18 | 지정된 파일을 클라이언트로 전송한다. | CMD_DOWN_YES | 0x19 |
| CMD_NEW_UPLOAD | 0xb3 | 클라이언트에서 전송하는 파일을 저장한다. | CMD_NEW_UPLOAD_YES | 0xb4 |
| CMD_NEW_DOWNLOAD | 0xb1 | 지정된 파일을 지정된 옵션(전송 속도, 분할 개수 ) 맞춰 클라이언트로 전송한다. | CMD_NEW_DOWNLOAD_YES | 0xb2 |
| CMD_SHELL | 0x1b | 주어진 명령어를 실행한 결과를 클라이언트로 전송한다. | CMD_SHELL_YES | 0x1c |
| CMD_LISTEN_PROXY_TRANS | 0x3f | callback 프록시 서버로서 행위를 수행한다. | CMD_LISTEN_PROXY_TRANS_YES | 0x40 |
| CMD_LISTEN_TRANS | 0x3d | callback 서버로서 행위를 수행한다. | CMD_LISTEN_TRANS_YES | 0x3e |
| CMD_TRANSFER | 0x1f | 클라이언트에게 접속 정보를 전달 받은 새로운 백도어에 패스워드를 전송하여 백도어를 실행시키도록 하고 이후 클라이언트에서 전송하는 패킷을 새로운 백도어로 전송하는 프록시 서버로서 동작한다. | CMD_TRANSFER_YES | 0x20 |
| CMD_PROXY_TRANSFER | 0x21 | 클라이언트에게 접속 정보를 전달 받은 새로운 백도어에 패스워드와 통신 설정 정보를 sha512 해시한 값을 보내고 이후 클라이언트에서 전송하는 패킷을 새로운 백도어로 전송하는 프록시 서버로서 동작한다. 이때 패킷의 9바이트를 "0000002C061E00000020"으로 채운다 | CMD_PROXY_TRANSFER_YES | 0x22 |
| CMD_SOCKS_PROXY_TRANSFER | 0x23 | 클라이언트에게 접속 정보를 전달 받은 새로운 백도어에 패스워드와 통신 설정 정보를 sha512 해시한 값을 보내고 이후 클라이언트에서 전송하는 패킷을 새로운 백도어로 전송하는 SOCKS5 프록시 서버로서 동작한다. 이때 패킷의 9바이트를 "0000002C061E00000020"으로 채운다 | CMD_SOCKS_PROXY_TRANSFER_YES | 0x24 |
| CMD_BACK | 0x13 | 응답코드를 클라이언트로 전송한다. | CMD_BACK_YES | 0x14 |
| CMD_SOCKS_PROXY | 0x29 | 클라이언트를 프록시로 사용한다. | CMD_SOCKS_PROXY_YES | 0x30 |
| CMD_SINGLE_CMD | 0x25 | 주어진 명령어를 실행한 결과를 클라이언트로 전송한다. 이후 백도어 프로세스를 종료한다. | CMD_SINGLE_CMD_YES | 0x26 |
| CMD_IN_SINGLE_CMD | 0x27 | 주어진 명령어를 실행한 결과를 클라이언트로 전송한다. | CMD_IN_SINGLE_CMD_YES | 0x28 |
| CMD_D_SINGLE_CMD | 0x2b | 주어진 명령어를 독립된 프로세스로 실행한다. | CMD_D_SINGLE_CMD_YES | 0x2c |
| CMD_D_OUT_SINGLE_CMD | 0x2d | 주어진 명령어를 독립된 프로세스로 실행한다. 이후 백도어 프로세스를 종료한다. | CMD_D_OUT_SINGLE_CMD_YES | 0x2e |
| CMD_NEW_SINGLE_CMD | 0x2f | 주어진 명령어를 실행한 결과를 클라이언트로 전송한다. | CMD_NEW_SINGLE_CMD_YES | 0x30

1.2.2. syslogk rootkit

work/mnt/hgfs/Desktop/tomcat20250414_rootkit_linux234/tomcat20250414_rootkit_linux2345/main.c 파일은 2022년 버전 루트킷과 동일하게 Netfilter 훅 지점에 콜백 함수를 등록한다. 변경된 점은 함수 후킹, 모듈 은닉, 새로 추가된 Netfilter 훅 지점이다.

2022 루트킷에서는 함수 후킹을 위해 udis86 라이브러리를 사용한 것과 달리 2025 루트킷에서는 khook 라이브러리를 사용하였다.

좌)2022 루트킷 udis86 라이브러리, 우) 2025 루트킷 khook 라이브러리

caption - 좌)2022 루트킷 udis86 라이브러리, 우) 2025 루트킷 khook 라이브러리

후킹 대상 함수가 3개였던 2022 루트킷과 달리 총 5개의 함수를 후킹한다. 후킹 관련 정보는 아래 표와 같다.

| 후킹 대상 함수 | 후킹 함수 | 행위 |
| --- | --- | --- |
| tcp4_seq_show | khook_tcp4_seq_show | 열린 포트 은닉 |
| proc_filldir | khook_proc_filldir | 프로세스 은닉 |
| filldir | khook_filldir | 디렉토리 은닉 |
| filldir64 | khook_filldir64 | 디렉토리 은닉 |
| proc_root_readdir | khook_proc_root_readdir | 디렉토리 은닉

Netfilter 훅은 기존 NF_INET_LOCAL_IN, NF_INET_LOCAL_OUT 2개의 훅 지점에서 2개가 추가되었다.

  • NF_INET_LOCAL_IN : 로컬 호스트로 패킷을 보내기 전

  • NF_INET_LOCAL_OUT : 로컬 호스트에서 패킷을 외부로 보내기 전

  • NF_INET_PRE_ROUTING : 패킷이 네트워크 스택에 들어온 직후

  • NF_INET_POST_ROUTING : 라우팅이 발생한 후 네트워크에 놓여지기 직전

hkcap.c 후킹 함수 등록 로직

caption - hkcap.c 후킹 함수 등록 로직

NF_INET_PRE_ROUTING 지점의 콜백 함수는 크게 2가지 행위를 한다.

첫 번째는 2022 루트킷의 NF_INET_LOCAL_IN 지점과 동일하게 패킷 조건을 검사하여 백도어를 실행한다. 패킷 조건이 복잡한 2022 루트킷과 달리 NF_INET_PRE_ROUTING 지점 콜백 함수는 TCP + SYN 패킷이고, id와 seq 값이 id_list, seq_list에 존재하는지 확인한다.

조건을 만족한 패킷의 window 값을 매직 패킷 값과 비교하여, 값에 따라 위장 프로토콜을 설정해 백도어를 실행한다. 매직 패킷 값에 따른 위장 프로토콜은 아래 표와 같다.

| 매직 패킷  | 위장 프로토콜 |
| --- | --- |
| 1022 | TCP |
| 2025 | HTTP |
| 29201 | HTTPS |
| 1130 | SSL |
| 2101 | SMTP

백도어를 실행하는 방법에도 큰 차이가 있다. /bin/sh -c 를 사용한 2022 루트킷과 달리, call_usermodehelper 함수로 백도어를 실행한다. 이때 포트는 3000 ~ 8000 사이의 랜덤한 포트를 사용한다.

hkcap.c kernel_run 함수

caption - hkcap.c kernel_run 함수

두 번째는 백도어와 통신 중인 ip에서 전송된 패킷의 dest 값을 위에서 생성된 랜덤 포트로 변경한다. 이는 공격자가 랜덤으로 생성된 백도어의 포트를 몰라도 통신을 할 수 있는 로직이다.

caption - 패킷의 dest 값을 변경하는 hkcap.c 코드

NF_INET_LOCAL_IN 지점의 콜백 함수는 NF_IP_PRE_ROUTING에서 변경된 패킷의 dest 값을 실제 백도어에서 사용하는 포트로 변경한다. 이는 2번에 걸쳐 포트를 바꾸며 실제 백도어가 사용하는 포트를 찾기 어렵게 하기 위함으로 보인다.

패킷의 dest 값을 백도어가 사용하는 포트로 변경하는 hkcap.c 코드

caption - 패킷의 dest 값을 백도어가 사용하는 포트로 변경하는 hkcap.c 코드

NF_INET_LOCAL_OUT 지점의 콜백 함수는 백도어가 보낸 패킷의 source 값이 백도어의 listen 포트이면, 임의 포트로 변경하고 아닌 경우에는 실제로 외부로 보낼 포트로 변경한다.

NF_INET_POST_ROUTING 지점의 콜백 함수는 source 값이 임의 포트로 변경된 패킷의 source 값을 실제로 외부로 보낼 포트로 변경한다.

caption - 패킷의 dest 값을 공격자의 포트로 변경하는 hkcap.c 코드

루트깃 은닉은 모듈 리스트에서 자기 자신을 제거하는 거 외에도 sysfs에서도 제거하는 함수가 추가되었다.

모듈 은닉 hkmod.c 코드

caption - 모듈 은닉 hkmod.c 코드

sysfs 관련 함수는 KoviD 루트킷의 kv_hide_mod 함수를 그대로 사용하였다.

KoviD 루트킷의 kv_hide_mod 함수

caption - KoviD 루트킷의 kv_hide_mod 함수

은닉된 루트킷에 특정 문자열을 작성하면 다시 모듈이 보이는 것은 동일하지만 작성된 문자열에 따라 Netfilter 훅에서 변경하는 포트의 값을 지정하는 기능이 추가되었다.

포트 값을 지정하는 hkcap.c 코드

caption - 포트 값을 지정하는 hkcap.c 코드

루트킷에 데이터를 작성할 때 모듈을 보이게 하는 문자열을 제외한 모든 문자열은 AES-CBC-256으로 복호화해 확인한다. AES key와 iv는 아래와 같다.

  • AES key: d03deb92153a71458973aef3857d75b27e552cc63e6158a8339811873994de47

  • iv: efa3c987532cc0bdac533845ad8df5ea

루트킷에 작성한 문자열에 따른 행위는 아래와 같다.

| 문자열 이름 | 문자열 | 행위 |
| --- | --- | --- |
| ModuleDecodeKey | mrdyZwIh2Bh | 루트킷 은닉 해제 |
| PROC_CMD_TRANSREG | h9WJZ1 | 현재 백도어와 통신 중인 공격자 정보의 ip, 포트와 백도어의 포트를 문자열 이후에 주어진 값으로 변경한다. |
| PROC_CMD_TRANSCLR | 64OWE2 | 백도어 포트가 문자열 이후에 주어진 값과 같은 공격자 정보를 현재 연결된 통신 정보 리스트에서 삭제한다. |
| PROC_CMD_CONNSET | C8lnS3 | 백도어 포트가 문자열 이후에 주어진 값과 같은 공격자 정보를 주어진 ip, 포트 값으로 변경한다

1.2.3. Backdoor Client

work/mnt/hgfs/Desktop/tomcat20250414_rootkit_linux234/tomcat20250414_rootkit_linux2345/work/tcat.c 파일은 백도어 클라이언트 소스코드이다. 2022 백도어 클라이언트와 비교하였을 때, kernel cmd 기능이 사라진 점 외에는 큰 변화가 없으며 옵션 또한 kc 옵션이 사라지고 LLL 옵션이 생겼다. 전체 옵션은 아래 표 참고 바란다.

| Long Option | Short Option | 의미 |
| --- | --- | --- |
| HOST | H | 백도어 주소 |
| PORT | P | 백도어 포트 |
| password | p | 백도어 접속 시에 사용할 패스워드 (기본: ssecuremw) |
| callback | c | callback 모드 활성화 |
| single_command | s | 주어진 명령어를 실행하도록 전송 |
| daemon_command | d | 주어진 명령어를 새로운 프로세스에서 실행 하도록 전송 (백그라운드 실행) |
| proxy | x | 프록시 서버 사용 |
| socks_proxy | y | socks5 프록시 서버 사용 |
| socks_aim_hostname | i | 사용할 socks5 프록시 서버의 주소 |
| socks_aim_port | o | 사용할 socks5 프록시 서버의 포트 |
| socks_username | u | socks5 프록시 서버에 로그인할 유저이름 |
| socks_password | a | socks5 프록시 서버에 로그인할 패스워드 |
| knock_protocol | k | 백도어 초기 접속 시도에 사용할 프로토콜 (0: TCP, 1: HTTP, 2: SSL, 3: HTTPS, 4: SMTP) |
| ethernet_interface | e | 사용할 이더넷 인터페이스 |
| cookie | 5 | HTTP 혹은 HTTPS를 사용할 때의 쿠키  |
| host | 6 | HTTP 혹은 HTTPS를 사용할 때의 selfdefined host  |
| main_protocol | m | 백도어 통신에 사용할 프로토콜 (0: TCP, 1: HTTP, 2: SSL, 3: HTTPS, 4: SMTP) |
| LLL | L | 로그파일 경로

앞서 언급한 거처럼 kernel cmd 기능이 삭제되어 백도어에 명령어를 전달하는 방식은 아래와 같이 3가지가 존재한다.

  • single cmd : 입력 받은 명령어를 백도어에서 실행하도록 전송한다.

  • single daemon cmd : 입력받은 명령어를 백도어에서 새로운 프로세스를 생성하여 실행하도록 전송한다.

  • input loop : exit을 입력 받을 때까지 무한히 명령어를 입력 받아 명령어에 따라 다른 행위를 수행한다.

2022 백도어 클라이언트는 오직 명령어만 입력받아 통신하였으나 2025 백도어 클라이언트는 명령어 별로 옵션을 지정할 수 있게 되어 명령어의 개수는 줄었지만 수행할 수 있는 행위는 더욱 정교해졌다. input loop에서 명령어에 따라 수행하는 행위는 아래 표와 같다.

| 명령어 | 행위 |
| --- | --- |
| shell | shell 명령어를 입력 받아 백도어에서 실행하도록 전송한다. |
| inc | 현재 연결된 모든 백도어의 연결 정보를 출력한다. |
| trans | 현재 연결된 백도어를 프록시 서버로 사용하여 새로운 백도어에 연결한다. |
| socks5 | 현재 연결된 백도어를 socks5 프록시 서버로 사용하여 새로운 백도어에 연결한다. |
| upload | 입력받은 경로의 단일 파일을 백도어에 업로드한다. |
| download | 입력받은 경로의 단일 파일을 백도어에서 다운로드한다. |
| nup | 입력받은 경로의 파일을 지정된 옵션에 맞춰 백도어에 업로드한다. |
| ndown | 입력받은 경로의 파일을 지정된 옵션 백도어에서 다운로드한다. |
| back | 현재 백도어 연결을 종료하고 프록시로 사용하던 백도어에 접속한다. |
| exit | 프로세스를 종료한다. |
| cookie | 쿠키 값을 설정하거나 현재 쿠키값을 출력한다. |
| host | 현재 연결된 백도어 정보를 출력한다

현재 tcat_new_send_file, tcat_new_recv_file과 같이 추가적으로 작성된 명령어의 주요 함수들이 정의가 안되어있어 추가된 기능의 상세 분석은 불가하였다.

통신할 때 매번 패스워드의 sha512 해시 값을 보내던 2022 백도어 클라이언트와는 달리 2025 백도어 클라이언트는 첫 연결 시에만 패스워드의 sha512 해시 값을 전송한다.

백도어와의 모든 통신은 2022 백도어 클라이언트와 동일하게 xor key(1101link)로 암호화되어 이루어진다. 하지만 3개의 프로토콜을 지정할 수 있는 2022 백도어 클라이언트와는 달리 2025 백도어 클라이언트는 아래 4개의 프로토콜 중 하나로 설정된다. 특히 OLD HTTP가 삭제되었으며 HTTPS, SMTP가 추가되었다.

  • TCP

  • HTTP

  • HTTPS

  • SMTP

1.3. Cobalt Strike

1.3.1. Cobalt Strike Loader

Rust로 작성된 로더 악성코드로, 내장된 쉘코드를 복호화하고 메모리에서 실행한다. Cobalt Strike Loader 파일 경로는 아래와 같다.

  • work/mnt/hgfs/Desktop/New folder (2)/DboRrmSS.exe

  • work/mnt/hgfs/Desktop/New folder (2)/m01QzOfI.exe

  • work/mnt/hgfs/Desktop/New folder (2)/voS9AyMZ.tar.gz

  • work/home/user/.cache/vmware/drag_and_drop/0pkbW4/3Powwovv.exe

  • work/home/user/.cache/vmware/drag_and_drop/gWMDML/GnAN3FhY.exe

  • work/home/user/.cache/vmware/drag_and_drop/QqiN9h/DboRrmSS.exe

  • work/home/user/.cache/vmware/drag_and_drop/rM0FG0/m01QzOfI.exe

Windows API를 호출 할 때, djb2 해시 값을 계산해 동적으로 로드하고, 쉘코드는 AES-CBC-256으로 복호화한다. 이후 복호화된 쉘코드를 메모리에서 실행한다.

Window API 로딩 함수

caption - Window API 로딩 함수

모든 Cobalt Strike Loader에서 사용하는 AES key와 iv는 아래 표와 같다.

| 파일 경로 | AES key | iv |
| --- | --- | --- |
| work/mnt/hgfs/Desktop/New folder (2)/DboRrmSS.exe | 0e9cae6dc34ae064b71604c221bca933f1ffcb63df4af7e6806fa94849d66924 | 33081eba056e1ad1c8e5618829b84030 |
| work/mnt/hgfs/Desktop/New folder (2)/m01QzOfI.exe | b5976580954f47b49a33a5c580610aeb77e8c2ef66e0ab337ddfb2d1851e7998 | 545644526a2a08dff2dd4e3a812be126 |
| work/mnt/hgfs/Desktop/New folder (2)/voS9AyMZ.tar.gz | ae49597d8c87d5cf23f485fa0fc138d06c13aa743405e3c9c44b3097ba008dcf | a61080d5ee883489ee8ce40f84569fc3 |
| work/home/user/.cache/vmware/drag_and_drop/0pkbW4/3Powwovv.exe | 6f627869fe1f05add75dc039fce970fce9bb4531d5601f894877aab3bc9dfe89 | 5fb9604da0d50bce37e53c194ab074b0 |
| work/home/user/.cache/vmware/drag_and_drop/gWMDML/GnAN3FhY.exe | 62609005b9ace1387a1065e6a72b7f796e12fd7c3b34b30a94af5d9112287b3c | 5990b37f9e73a20c2d5605cba72399cc |
| work/home/user/.cache/vmware/drag_and_drop/QqiN9h/DboRrmSS.exe | 0e9cae6dc34ae064b71604c221bca933f1ffcb63df4af7e6806fa94849d66924 | 33081eba056e1ad1c8e5618829b84030 |
| work/home/user/.cache/vmware/drag_and_drop/rM0FG0/m01QzOfI.exe | b5976580954f47b49a33a5c580610aeb77e8c2ef66e0ab337ddfb2d1851e7998 | 545644526a2a08dff2dd4e3a812be126

1.3.2. Shellcode

Cobalt Strike Loader에 의해 실행되는 쉘코드로, Cobalt Strike Beacon을 복호화하여 메모리에서 실행한다. 파일 형태로 존재하는 쉘코드 경로는 아래와 같다.

  • work/home/user/.cache/vmware/drag_and_drop/5wdgDr/payload.bin

  • work/home/user/.cache/vmware/drag_and_drop/6bX9mm/Black.x64.exe

  • work/home/user/.cache/vmware/drag_and_drop/NDBu65/payload.bin

  • work/home/user/.cache/vmware/drag_and_drop/yf91yD/payload.bin

  • work/home/user/.cache/vmware/drag_and_drop/zlLWeR/payload.bin

work/home/user/.cache/vmware/drag_and_drop/6bX9mm/Black.x64.exe는 다른 쉘코드 파일들과 행위가 동일하지만 쉘코드가 아닌 컴파일된 실행 파일이다.

Black.x64.exe DIE 결과

caption - Black.x64.exe DIE 결과

Cobalt Strike Loader와 마찬가지로 djb2 해시 값을 계산해 Windows API를 동적으로 로드한 뒤 호출하고, 이후 Cobalt Strike Beacon을 RC4로 복호화해 메모리에서 실행한다.

RC4 ksa 함수

caption - RC4 ksa 함수

모든 쉘코드에서 사용하는 RC4 key는 아래 표와 같다. 파일 형태로 존재하지 않고, Cobalt Strike Loader에 의해 실행되는 쉘코드도 포함하였다.

| 파일 경로 | RC4 key |
| --- | --- |
| work/mnt/hgfs/Desktop/New folder (2)/DboRrmSS.exe | QGWYqgYFSXgkEvmll |
| work/mnt/hgfs/Desktop/New folder (2)/m01QzOfI.exe | bVeuvyKwejaykgTg |
| work/mnt/hgfs/Desktop/New folder (2)/voS9AyMZ.tar.gz | YPJngQShuxKNEcPw |
| work/home/user/.cache/vmware/drag_and_drop/0pkbW4/3Powwovv.exe | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/gWMDML/GnAN3FhY.exe | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/QqiN9h/DboRrmSS.exe | QGWYqgYFSXgkEvmll |
| work/home/user/.cache/vmware/drag_and_drop/rM0FG0/m01QzOfI.exe | bVeuvyKwejaykgTg |
| work/home/user/.cache/vmware/drag_and_drop/5wdgDr/payload.bin | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/6bX9mm/Black.x64.exe | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/NDBu65/payload.bin | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/yf91yD/payload.bin | bVeuvyKwejaykgTg |
| work/home/user/.cache/vmware/drag_and_drop/zlLWeR/payload.bin | xYHuKSOLEkSzrxVq

쉘코드에 의해 실행되는 Cobalt Strike Beacon은 ETW 관련 함수와 AMSI.dll 함수를 패치하여 Windows 보안 기능을 우회하는 로직이 존재한다. 하지만 암호화된 설정 정보가 온전하지 않아 설정 정보는 확인할 수 없었다.

1.3.3. Cobalt Strike Beacon

work/mnt/hgfs/Desktop/111/beacon 디렉토리 내에 Cobalt Strike Beacon 소스코드가 존재한다. Beacon 실행 시 설정 정보는 1바이트 xor 키(46)로 복호화되고, 복호화된 설정 정보는 블록으로 구성되며, 각 블록은 아래와 같은 구조를 갖는다.

struct Block {
  ushort index;
  ushort data_type;
  ushort data_size;
  union { // data
    typedef unsigned short s; // short
    typedef unsigned int i;  // int
    char d[]; // string
  } value

복호화된 설정 정보는 파싱하여 전역변수에 저장된 후 필요한 값이 있을 때 인덱스로 접근하여 사용한다. CobaltStrikeParser를 이용해 파싱한 일부 설정 정보는 아래와 같다.

CobaltStrikeParser 결과

caption - CobaltStrikeParser 결과

복호화된 설정 정보에는 C&C 서버 주소와 엔드포인트가 포함되어 있으나, 실행 과정에서 해당 값은 사용되지 않고 소스코드에 하드코딩된 값을 사용한다.

comm.cpp send_Metadata 함수

caption - comm.cpp send_Metadata 함수

설정 정보 복호화가 완료되면 C&C 서버에 전송할 메타데이터를 생성한다. 주요 메타데이터 정보는 아래 표와 같다.

| 메타데이터 변수 이름 | 설명 |
| --- | --- |
| beacon_key | 무작위 16바이트(세션 ) |
| codepage | 65001 |
| oem | 65001 |
| beacon_id | 무작위 4바이트(에이전트 식별 id) |
| MajorVersion | MajorVersion(uname 결과) |
| dwBuildNumber | dwBuildNumber(uname 결과) |
| build | build(uname 결과) |
| machine | x86_64인 경우 2, 기본 0 |
| hostinfo | 아이피 주소 |
| ComputerName | 컴퓨터 이름 |
| UserName | 유저 이름 |
| ProcessName | 프로세스 이름

메타데이터 생성이 완료되면 C&C 서버로 전송하고, 이후 5초마다 HTTP 기반 폴링 통신을 수행한다. 이때 명령 수신에는 GET, 결과 전송에는 POST가 사용된다. 수신한 명령 id에 따라 행위를 수행하고, 결과를 C&C 서버로 전송한다. 전체 행위는 아래 표와 같다.

| id | 함수 이름 | 상태 | 행위 |
| --- | --- | --- | --- |
| 5 | cd | 동작 | 작업 디렉토리를 주어진 경로로 변경한다 |
| 10 | upload | 비활성 | 파일 업로드 관련 코드가 주석 처리되어 있다. |
| 11 | download | 비활성 | 파일 다운로드 관련 코드가 주석 처리되어 있다. |
| 12 | execute | 미구현 | BeaconExecuteCommand 함수가 정의되지 않았고 관련 코드는 주석 처리되어 있다. |
| 14 | proxy_connect | 미구현 | 정의되지 않은 함수를 호출하는 코드가 주석 처리되어 있다. |
| 15 | proxy_write | 미구현 | 정의되지 않은 함수를 호출하는 코드가 주석 처리되어 있다. |
| 16 | proxy_close | 비활성 | 전역변수 gBeaconRportfwd의 state를 0으로 변경하는 코드가 주석 처리되어 있다. |
| 17 | proxy_listen | 미구현 | 정의되지 않은 함수를 사용하는 코드가 주석 처리 되어있다. |
| 39 | pwd | 동작 | 현재 작업 경로를 출력한다. |
| 53 | ls | 동작 | 주어진 경로의 파일 목록을 출력한다. |
| 100 | bof | 미구현 | 정의되지 않은 함수를 호출하는 코드가 주석 처리되어 있다

절반 이상의 행위가 미구현 또는 비활성화되어 있으며, Cobalt Strike Beacon은 미완성 악성코드로 추측된다.

1.3.4. C# Loader

work/home/user/Desktop/0128 디렉토리 내에는 다른 프로세스에 Cobalt Strike Beacon을 인젝션하는 C# 로더(ok.dll)와 소스코드(ok.cs), 해당 로더를 실행하는 스크립트(ok.sct) 등이 존재한다.
함수 이름과 인자 값에 test 문자열이 다수 포함되어 있어 테스트용 파일로 추정된다.

ok.hta

hxxp://192.168[.]123.200/ok.sct에 접속하여 스크립트를 다운받아 Fuk 함수를 실행한다.

ok.hta 파일 내용

caption - ok.hta 파일 내용

다운받는 스크립트는 같은 경로에 있는 ok.sct로 추정된다.

ok.sct

직렬화된 C# 객체를 역직렬화한 뒤 Work 함수를 실행한다. 이때 실행되는 파일은 ok.cs를 컴파일한 ok.dll이다. ok.cs에서 Work 함수를 확인할 수 있다.

C# Loader 역직렬화 코드

caption - C# Loader 역직렬화 코드

C# 로더는 conhost.exe 프로세스에 Cobalt Strike Beacon을 인젝션하여 실행한다. Cobalt Strike Beacon은 zlib 압축 및 base64 인코딩되어 문자열로 저장되어 있다.

Cobalt Strike Beacon 디코딩 함수

caption - Cobalt Strike Beacon 디코딩 함수

CobaltStrikeParser를 이용해 파싱한 Cobalt Strike Beacon의 일부 설정 정보는 아래와 같다.

C# Loader에 저장된 Cobalt Strike Beacon 설정 정보

caption - C# Loader에 저장된 Cobalt Strike Beacon 설정 정보

1.4. Ivanti Connect Secure

1.4.1. BrushFire

work/mnt/hgfs/Desktop/ivanti-new-exp-20241220.zip 파일의 압축을 해제하면 Ivanti Connect-Secure RCE 취약점을 악용하여 백도어를 유포하는 exp*.py 파일을 확인할 수 있다.

python 스크립트에서 clientCapabilities 값의 길이를 256바이트를 초과하도록 설정하는데, 이는 clientCapabilities에서 발생하는 256바이트 버퍼 오버플로우로 인해 RCE로 이어지는 CVE-2025-0282를 사용하는 것으로 보인다.

caption - ex*.py 로직

CVE-2025-0282는 과거 중국 배후로 알려진 해킹 그룹 UNC5221에서 공격의 초기벡터로 사용한 적이 있는 취약점이다.

해당 취약점을 악용하는 모든 exp*.py는 행위가 동일하고, 익스플로잇에 사용되는 함수 오프셋 혹은 접속하는 서비스의 엔드포인트에만 차이가 존재한다. 실행 인자에 따른 exp*.py 행위는 아래 표와 같다.

| long option | short option | 행위 |
| --- | --- | --- |
| ip | i | vpn 서버 ip |
| port | p | vpn 서버 포트 |
| leak |  | 메모리 정보를 leak하여 config에 저장한다. |
| detect_version |  | 취약점이 존재하는 버전의 vpn인지 확인한다. |
| install |  | 백도어를 설치한다. |
| check |  | config에 저장된 정보가 올바른지 확인한다. |
| check_path |  | 취약점이 정상적으로 동작하는지 확인한다. |
| delay |  | timeout 시간을 지정한다. |
| check_backdoor |  | 백도어가 설치되어있는지 확인한다. |
| cmd |  | 주어진 명령어를 root권한으로 실행하도록 한다. |
| python |  | 주어진 파일을 per-static으로 실행하도록 한다. |
| perl |  | 주어진 파일을 per-static으로 실행하도록 한다. |
| lcmd |  | 주어진 명령어를 실행하도록 한다. |
| version |  | /home/ssl-vpn-VERSION 파일의 내용을 받아 출력한다. |
| config |  | dsls -B -R -S / 명령어의 실행 결과를 출력한다. |
| local_path |  | 파일을 업로드 업로드할 로컬 파일 경로 |
| upload |  | 파일을 업로드한다. |
| download |  | 파일을 다운로드한다. |
| clear_log |  | 해당 프로그램을 사용하며 남은 로그를 삭제한다. |
| clean |  | 해당 프로그램을 사용하며 남은 모든 흔적을 삭제한다. |
| verbose | v | 사용되지 않는다

백도어는 plugins/ssl_read 파일로, RCE로 트리거된 plugins/install 파일이 백도어를 ssl_read 함수에 후킹한다. 후킹된 ssl_read 함수는 ssl 통신을 수신할 때, 배포 시 설정된 매직 바이트 값이 존재하는 패킷을 탐지하면 exp*.py에서 무작위로 생성된 4바이트 키와 xor하여 쉘코드를 실행한다.

caption - ssl_read 쉘코드 실행 로직

exp*.py는 실행 인자에 따라 쉘코드를 생성하고 공격 대상 서버에 업로드한다. 이때 쉘코드의 하드코딩된 패턴을 replace 함수를 사용하여, 유효하지 않은 코드를 공격 대상에 유효한 값으로 변경한다. plugins 내에 존재하는 쉘코드를 업로드하는 exp*.py 실행 인자와 쉘코드 행위는 아래 표와 같다.

| 실행 인자 | 쉘코드 이름 | 쉘코드 행위 |
| --- | --- | --- |
| install | install | ssl_read 함수 주소를 ssl_read 쉘코드로 덮어쓴다. |
| install | leak_info | inject 쉘코드를 실행하기 위해 함수 메모리 주소를 전송한다. |
| install | inject | ssl_read 쉘코드를 leak된 메모리의 ssl_read에 덮어쓴다. |
| install | patch | inject에서 ssl_read를 메모리에 인젝션할 사용되는 strncpy 함수이다. |
| version, download | read_file_asm | 지정된 파일의 데이터를 읽어 4바이트 키로 암호화하여 전송한다. |
| python, perl, upload | write_file_asm | 지정된 경로에 암호화된 파일을 4바이트 키로 복호화하여 저장한다. |
| upload, lcmd, cmd, config | cmd_asm | 지정된 명령어를 실행하고 4바이트 키로 암호화하여 결과를 전송한다

plugins/ssl_read백도어 행위는 UNC5221 공격에서 사용된 BrushFire 악성코드와 동일하며, 공격에서 확인된 BrushFire 파일 경로와 ssl_read 파일 경로가 동일하다.

1.4.2. SPAWN Family

work/mnt/hgfs/Desktop/New folder/203.234.192.200_client.zip 파일 내에는 터널링 스크립트와 ssh 클라이언트, 인증서 파일이 압축되어 있다. readme.txt 파일에는 client.py, controller.py를 사용하여 203.234[.]192.200 ip에 접속하는 방법이 적혀있으며 해당 ip는 한겨레 신문사의 ip 주소로 확인되었다.

readme.txt 파일 내용

caption - readme.txt 파일 내용

client.py는 네트워크 트래픽을 터널링하기 위한 SOCKS5 프록시를 실행하고, client.py의 옵션별 기능은 아래와 같다.

| 옵션 | 기능 | 기본값 |
| --- | --- | --- |
| -h | 로컬 바인드 주소 | 127.0.0.1 |
| -p | 로컬 바인드 포트 | 1080 |
| -H | 접속할 프록시 주소 | raddr |
| -P | 접속할 프록시 포트 | 443 |
| -v | 디버깅 로그 활성화 |  |
| --ca | ca 인증서 경로 |  |
| --cert | client 인증서 경로 |  |
| --key | client 개인키 경로

client.py에는 client_hello와 client_key_exchange 메시지 템플릿이 사전 정의되어 있다. 동작은 아래와 같다.

  • client_hello

    • 인덱스 11–15 위치에, 인덱스 15–43(랜덤 28바이트)에 대한 CRC32 값을 삽입한 뒤 전송한다.

    • sessionid 변수에 랜덤 32바이트를 저장하고, 이를 메시지에 포함해 전송한다.

  • client_key_exchange

    • premaster 필드에 랜덤 256바이트, enc_handshake_msg 필드에 랜덤 32바이트를 포함해 전송한다.

client.py에 정의된 client_hello 초기 값은 UNC5221 공격에서 확인된 SPAWNMOLE의 매직 패킷과 일치하다. 실행 시에는 client_hello의 인덱스 15–43 구간에 랜덤 28바이트를 기록하고, 그에 대한 CRC32를 인덱스 11–15에 덮어써 전송하는데, 이 방법은 JPCert 블로그 글에서 보고된 SPAWNCHIMERA 설명과 유사하다.

SPAWNCHIMERA 역시 SPAWNMOLE과 동일하게 UNC5221 공격에 사용된 악성코드로, 앞서 ssl_read 백도어 유포에 악용된 CVE-2025-0282 취약점을 통해 유포된 악성코드이다.

controller.py는 client.py에서 실행된 SOCKS5 프록시를 통해서 ssh 서버와 통신하는 클라이언트 스크립트이다.

ssh 서버에 접속하는 controller.py

caption - ssh 서버와 통신하는 controller.py

SPAWNMOLE은 ssh 서버를 생성하는 SPAWNSNAIL과 함께 확인되었고, SPAWNCHIMERA는 자체적으로 ssh 서버를 구동하는 기능을 포함한다. 이에 따라 controller.py는 SPAWNSNAIL 또는 SPAWNCHIMERA가 제공하는 ssh 서버에 접속하는 클라이언트로 판단된다.

1.4.3. ROOTROT Client

work/mnt/hgfs/Desktop/ivanti_control/main.py는 입력받은 명령어에 따라 Perl 스크립트 조각을 생성하고, 이를 Base64로 인코딩하여 특정 쿠키 값에 설정한 뒤 HTTP GET 요청을 전송한다. 명령어에 따른 Perl 스크립트는 다음과 같다.

  • download {file_path}

my $s = "";
my $path = "{file_path}";
local *FH;open(*FH, "<". $path);
while (<FH>) { $s.= "$_";}
close(*FH);
print  "<!-- ".MIME::Base64::encode_base64($s,"")."-->"

  • {command}

    my $l ="";
    my $r = "";
    my $fd = popen(*PIPE,"{command}",\\'r\\') ;
    while (<PIPE>) {$l.= $_;};
    $r.= MIME::Base64::encode_base64($l,"");
    my $s = "<!-- ". pack("C*", unpack("C*", $r));$s.= "-->";
    print $s
    
    

두 Perl 스크립트 모두 실행 결과를 base64로 인코딩한 뒤 html 주석으로 감싸 출력한다. 이후 main.py는 서버 응답을 수신하면 본문 마지막 html 주석을 추출하여 base64 디코딩한 후 사용자에게 출력한다.

요청을 보내고 주석의 내용을 디코딩하는 함수

caption - 요청을 보내고 주석의 내용을 디코딩하는 함수

Perl로 작성된 웹쉘인 점, 쿠키 값을 base64로 디코딩하여 eval로 실행한다는 점, 실행 결과가 base64로 인코딩되어 html 주석으로 전달된다는 점을 토대로 main.py UNC5221 공격에 사용된 ROOTROT 악성코드의 클라이언트 스크립트로 추정된다.

또한 ROOTROT 역시 앞서 언급한 UNC5221 공격에 사용된 악성코드들과 마찬가지로 Ivanti Connect Secure 취약점을 악용한 공격에서 발견되었다.

1.5. 피싱 공격

1.5.1. 네이버 중간자 공격

work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing 디렉토리 내에 있는 파일은 네이버 계정을 노린 중간자 공격과 관련있는 파일들이다. 특히 work/mnt/hgfs/Desktop/New folder/readme.txt 파일에서 중국어로 작성된 설정 방법을 확인할 수 있다.

readme.txt 파일 내용

caption - readme.txt 파일 내용

readme.txt 파일 내용 번역 결과는 아래와 같다.



work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/cipherginx.py 파일은 C&C 서버에서 실행되고, 네이버와 피해자 사이에서 HTTPS 프록시 서버로 동작한다. 피해자가 C&C 서버를 통해 네이버 서비스에 접속하면 헤더와 쿠기, 그리고 아이디와 비밀번호가 cookies 디렉토리에 저장된다. 각 파일 이름은 아래와 같다.

  • [nid_id].headers

  • [nid_id].cookie

  • accounts.txt

cipherginx.py accounts.txt 생성 로직

caption - cipherginx.py accounts.txt 생성 로직

work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/naverconfig.py 파일에 네이버 계정 정보를 탈취하는 자바스크립트 코드가 존재하고, 피해자가 로그인 페이지에 접속하면 원본 페이지에 해당 자바스크립트 코드를 삽입한다.

naverconfig.py 계정 정보 탈취 자바스크립트 코드

caption - naverconfig.py 계정 정보 탈취 자바스크립트 코드

탈취한 계정 정보를 이용해 피해자 네이버 메일, 주소록, 로그인 정보 등을 탈취하는 파일이 존재한다. 각 파일 경로와 설명은 아래 표와 같다.

| 파일 경로 | 설명 |
| --- | --- |
| work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/downloader/download.py | 이메일 수집, 주소록 덤프, 로그인 기록 수집 기능의 파일 |
| work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/downloader/gen_eml.py | download.py를 통해 수집한 이메일을 html에서 eml 파일로 패키징 |
| work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/downloader/manuel_download.py | 인자로 받은 nid에 대한 모든 이메일 수집 |
| work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/downloader/models.py | 중복 수집을 방지하기 위해 수집한 계정 정보와 mailSN을 his.db에 저장

1.5.2. 카카오 로그인 피싱 공격

vps/var/www/html/templates/kakao.html 파일은 카카오 로그인 페이지 템플릿이고, vps/var/www/html/kakao-login.php 파일은 카카오 로그인 처리 서버측 스크립트로 확인되었다. kakao-login.php는 GET 요청 시 “ft” 쿠키 존재 여부를 검사하며, 값이 존재할 경우 “https://mail.daum.net”으로 리다이렉트 한다.

GET 요청 처리 코드

caption - GET 요청 처리 코드

“ft” 쿠키 값이 존재하지 않으면 카카오 로그인 페이지를 보여주고, 사용자가 로그인을 시도하면 입력한 로그인 정보가 POST 요청 본문에 담겨 피싱 페이지로 전송된다.

POST 요청을 받으면 로그인 정보를 log/password_log.txt 파일에 작성하고, ft 쿠키 값을 no로 설정한다.

POST 요청 처리 코드

caption - POST 요청 처리 코드

password_log.txt 파일에 14개의 로그인 정보가 존재하나, 이 중 하나의 로그인 정보를 제외한 모든 내용은 테스트용으로 보인다. 123123, ttttt222, qwqwqw111 등의 데이터가 존재한다.

1.5.3. 피싱 이메일 공격

vps/var/www/html 디렉토리 내에는 피싱 이메일 생성과 피해자 관리 목적의 웹서버 파일이 존재한다.

웹서버에서 동작하는 파일은 로그 파일을 생성하고, generator.php를 제외한 나머지 파일의 로그 형식은 아래와 같다.

========[영문  이름] [일자], [연도], [:] [am/pm]====================
[appendix (옵션)]
[client ip]
[Header-Name]: [Header-Value] <- with_headers=true일 
[parameters]

로그 파일은 생성될 때 .txt 확장자 뒤에 날짜 정보가 추가된다.

generator.php

vps/var/www/html/generator.php파일은 피싱 이메일을 생성하는 파일이다. “HnoplYTfPX” 쿠키 값과 요청 메소드에 따라 실행 로직이 다르다. 아래 표 참고 바란다.

| HnoplYTfPX 쿠키 존재 여부 | 요청 메소드 | 기능 |
| --- | --- | --- |
| X | POST | passwd 파라미터 존재 “hkjOPQIBBgU”와 비교 , 일치하면 HnoplYTfPX 쿠키 설정  |
| O | GET | 피싱 이메일을 생성할 있는 관리자 출력 |
| O | POST | 피싱 이메일 생성 정보 출력

피싱 이메일 생성 시 정보를 입력받는 form의 형식은 아래 표와 같다.

| name | label | tag name | value |
| --- | --- | --- | --- |
| to_email_address | to email_address | input type="text" |  |
| onlysniff | only sniff | input type="checkbox" |  |
| phishing_url | Phishing URL | input type="text" | https://nid.navermails.com/nidlogin.login?mode=form&url= |
| redirect_url | Redirect URL | input type="text" | https://bigfile.mail.naver.com/bigfileupload/download?fid=9leqBrJ4Wrdjb4kOM6kmK3Y/aAu/KIYZKAurKxUwFxgXKxbmKCYwFA2dFAg9axvlHruqMq0opx2wazUlMovlFzK/K4MlKqMlp6JoKx0CMq04 |
| content | html content %img% %link_url% | textarea  |  |
| saveForm |  | input type="submit" | Submit

피싱 이메일 생성이 완료되면 사용자에게 출력되는 정보는 아래 표와 같다.

| h2 | 내용 |
| --- | --- |
| dot image url: | {base_url}/request.php?i={b64encode(to_email_address)}&dot.png를 가리키는 img 태그 |
| redict url: | {base_url}/bigDataDownload/{b64encode(to_email_address)} |
| link url: | {phishing_url}{urlencode(redirect_url)}&login_id={b64encode(to_email_address)}{"&f=yes" if onlysniff else ""} |
| content: | content에서 img를 dot image url의 내용, link_url를 link url의 내용으로 대입한 결과

피싱 이메일 내용이 content로 보이며, 피싱 이메일이 생성되면 log/generator_log.txt 파일에 로그를 작성한다. 로그 형식은 아래와 같다.

--------[영문  이름] [일자], [년도], [:] [am/pm]----------------
[to_emailaddress]_[b64encode(to_email_address)]
[link url]
[client ip]

또한 공격 대상이 피싱 이메일을 열람하였을 때 이메일 클라이언트가 외부 리소스를 로드하도록 설정되어 있으면 request.php가 로드된다.

request.php

vps/var/www/html/request.php 파일은 공격 대상의 피싱 이메일 열람 여부를 로그로 남기는 파일이다. 요청이 오면 log/request_log.txt 파일에 로그를 작성한다.

URI에 “.png”가 존재하면 무작위 색 이미지를 반환하고 이외의 경우 공격 대상의 브라우저에 따라 정보를 수집하는 자바스크립트 파일을 로드한다. 로드되는 자바스크립트 파일과 수집 정보는 아래 표 참고 바란다.

| path | info |
| --- | --- |
| payload/adobe.js | Adobe Reader 버전 확인(플러그인/ActiveX 탐지) |
| payload/flash.js | Adobe Flash 버전 확인 |
| payload/java.js.bak | JRE 버전 확인 |
| payload/plugin.js | 브라우저 플러그인 목록·버전 열람(navigator.plugins) |
| payload/xecure.js | XecureWeb 설치 여부·버전 확인 |
| payload/ie/drive.js.bak | 드라이브 문자 열람(파일시스템/WMIC ) |
| payload/ie/office.js | Microsoft Office 버전 조회(COM/ActiveX, Word.Application) |
| payload/ie/pdf.js | Adobe Reader 확인(ActiveX AcroPDF.PDF) |
| payload/ie/xecure.js | XecureWeb ActiveX 버전 확인 |
| payload/ie/xmldom.js | 프로그램/구성 정보 수집(MSXML/COM/레지스트리 활용 추정) |
| payload/non-ie/webrtc.js.bak | WebRTC로 로컬·공인 IP 수집(RTCPeerConnection ICE 후보)

수집한 정보는 results 변수에 저장되고, 이후 response.php?i={b64encode(email)}&{results} 로 요청을 보낸다.

response.php

vps/var/www/html/response.php 파일은 공격 대상의 ip, user-agent, locale를 검사하여 조건을 만족하면 i 파라미터 값을 base64 디코딩한 뒤 log/response_log.txt 파일에 로그를 작성한다. 조건은 다음과 같다.

  1. locale이 화이트리스트에 포함된 경우

  2. ip, user-agent가 블랙리스트에 포함되지 않은 경우

locale 화이트리스트는 아래 참고 바란다.

  • us

  • jp

  • ja

  • kr

  • ko

user-agent 블랙리스트는 아래 참고 바란다.

  • bot

  • spider

  • crawl

  • trend

  • symantec

  • virus

  • spam

  • secure

ip 블랙리스트는 아래 표 참고 바란다.

| ip range | info | ip range | info |
| --- | --- | --- | --- |
| 103.246.39.0/24 | bluecoat | 222.122.128.0/24 | Korea Telecom KORNET |
| 104.132.0.0/14 | Google | 222.122.185.0/24 | Korea Telecom KORNET |
| 121.156.79.0/24 | Korea Telecom KORNET | 23.192.0.0/11 | Akamai |
| 125.141.228.0/24 |  | 52.84.0.0/14 | Amazon |
| 15.211.153.0/24 | HP | 52.88.0.0/13 | Amazon |
| 150.70.0.0/16 | Trend Micro | 54.192.0.0/16 | Amazon |
| 182.162.206.103 | with symantec client v1 | 59.12.199.252 |  |
| 182.162.206.39 | with symantec client v2 | 59.18.85.26 |  |
| 183.99.48.26 | 20170124 fake chrome | 61.98.76.115 |  |
| 199.19.248.0/21 | bluecoat 199.19.248.0/21 | 61.111.0.0/18 | LG DACOM KIDC |
| 200.63.47.32/32 | AboveNet Engineering  ipeng@zayo.com | 66.102.0.0/20 | Google |
| 210.121.169.0/24 |  | 66.249.64.0/19 | Google |
| 211.115.119.0/24 | jnjedu | 72.13.86.0/24 | ??echosting |
| 211.233.80.0/ | Ahnlab | 83.223.96.0/19 | Gyron |
| 211.55.19.0/24 | KT CUSTOMER | 89.145.108.0/22 | Gyron |
| 216.104.0.0/19 | Trend Micro | 89.148.104.0/21 | DUNAWEB_89.148.104.0/21 |
| 218.153.8.0/24 |  | 115.90.74.131 |  |
| 221.141.3.0/24 | SK Broadband Co Ltd | 211.249.40.0/24 | naver |
| 221.252.42.0/24 | NRI SecureTechnologies | 211.56.96.0/24 | kakao |
| 222.99.190.104 | 20170124 chrome alike testing

피싱 공격에 사용된 파일과 로그, 실제 피해 로그를 종합한 정보는 아래 표와 같다.

| 로그 파일 경로 | 관련 파일 | 실제 피해(추정)/로그 개수 |
| --- | --- | --- |
| log/generator_log.txt | generator.php | 220/315 |
| log/password_log.txt | kakao-login.php | 1/14 |
| log/request_log.txt | redirect.php, request.php | 42/536 |
| log/response_log.txt | response.php | 4/33

1.6. 연세대학교 이메일 탈취 악성코드

vps/var/www/html/js/chks.js 파일은 “https://mail.yonsei.ac.kr/common/json/agent.do”에서 유저 정보를 수집하고, 이메일 포워딩 주소 목록에 cimoon185@daum.net 를 추가한다.

chks.js Forward 함수

caption - chks.js Forward 함수

이후 ent_{user}, TOTAL 메일함에서, 발송일이 2017-10-01 이후인 이메일을 탈취해 https://service.navers.org/emuy.php?i={user}로 전송한다.

2. 탈취 자료 분석

본 장에서는 공격자 VMware VM 덤프 파일에서 확인한 자료 중, 일반적으로 확보가 불가능하거나 어려운 데이터를 “탈취 자료”로 정의하고 이를 정리한 내용을 다룬다.

2.1. 외교부 AyersRock Mail

/work/mnt/hgfs/Desktop/mofa.go.kr.7z 파일 내에는 외교부 내부에서 사용한 것으로 추정되는 AyersRock Mail 솔루션 소스코드가 존재한다. AyersRock Mail은 (주)나라비전에서 2016년 Kebi Mail을 기반으로 한 웹메일 솔루션이다.

압축 해제 후 확인한 디렉토리 구조가 kebi-batch, kebi-cor, kebi-web-mail 등 Kebi 계열 모듈로 구성되어 있고, 각 디렉토리 하위에 있는 app-*.xml 파일 mail 요소의 host 값에서 mofa.go.kr을 확인할 수 있다.

app-*.xml mail 요소의 host 값

caption - app-*.xml mail 요소의 host 값

mofa.go.kr/kebi-web-parent/mail/document/info.txt 파일에는 내부 개발 서버, DB, 테스트 서버 등 여러 계정 정보와 메모가 작성되어 있다.

info.txt 파일 내용

caption - info.txt 파일 내용

mofa.go.kr/kebi-web-parent/mail/document/worklist.txt 파일에는 작업 일정과 작업 목록에 대한 메모가 작성되어 있다.

worklist.txt 파일 내용

caption - worklist.txt 파일 내용

Git 저장소와 관련있는 .gitignore, .gitmodules 파일이 존재하고, 개발사에서 작성한 것으로 추정되는 info.txt, worklist.txt 파일이 존재하여 mofa.go.kr.7z은 개발사 Git 저장소에서 탈취한 파일을 압축한 것으로 보인다. 다만 .git 폴더가 존재하지 않아 저장소 유형은 알 수 없다.

2.2. 시큐어키 APPM & iRASS

2.2.1. LG U+ APPM 서버 소스코드

/work/mnt/hgfs/Desktop/111/ROOT/ROOT 디렉토리 내에는 LGU+ 내부에서 사용한 것으로 추정되는 secureki의 통합 패스워드 관리 솔루션인 APPM 소스코드가 존재한다.

/work/mnt/hgfs/Desktop/111/ROOT/ROOT/WEB-INF/classes/language/view.properties 파일에서 “LG U+ 커스텀 테이블” 키워드를 확인할 수 있다. 이 외에도 /work/mnt/hgfs/Desktop/111/ROOT/ROOT/META-INF/maven/com.sgt/appmui_i18n_Transformation_*/pom.xml 파일에서 “LGU플러스” 키워드를 확인할 수 있다.

view.properties 파일 내용

caption - view.properties 파일 내용

2.2.2. APPM & iRASS 서버 관리 프로그램

/work/mnt/hgfs/Desktop/111/home/home 경로에 appm, irass 디렉토리가 존재하는데, 각각 APPM, iRASS 서버 관리가 목적으로 추정되는 프로그램이 존재한다.

APPM

/work/mnt/hgfs/Desktop/111/home/home/appm 디렉토리 내에는 appm_master 바이너리와 암호화 관련 모듈이 있는 crypto 디렉토리가 존재한다. appm_master는 실행 과정에서 여러 바이너리와 설정 파일 존재를 확인하는데, 공개된 자료에서는 다수의 파일이 존재하지 않는다. 주요 설정 파일로는 클라우드 설정 파일, 복호화된 appmkey 파일이 있으나 존재하지 않는다.

공개된 파일 목록

caption - 공개된 파일 목록

iRASS

/work/mnt/hgfs/Desktop/111/home/home/irass/bin 디렉토리 내에는 irass_master 바이너리를 포함해 irass_db, irass_admin, irass_rdp 등 여러 바이너리가 존재한다. 모두 iRASS 관련 바이너리이다. 특히 history.txt에 “KB국민은행” 키워드가 존재하고, 개발사에서 작성한 이슈 및 패치 메모 파일로 추정된다.

history.txt 파일 내용

caption - history.txt 파일 내용

2.2.3. LG U+ ACCOUNT 테이블 덤프

/work/mnt/hgfs/Desktop/111/account/account.txt, /work/mnt/hgfs/Desktop/111/account (2).txt 두 파일에는 LG U+ ACCOUNT 테이블 데이터가 작성되어 있다.

account.txt 파일 내용

caption - account.txt 파일 내용

/work/mnt/hgfs/Desktop/111/ROOT/ROOT/WEB-INF/classes/ibatis/sql/sql-account.xml 파일에서 확인한 ACCOUNT 테이블 관련 쿼리와 account.txt에서 확인한 스키마를 비교한 결과 LG U+ ACCOUNT 테이블 데이터인 것을 알 수 있다.

2.2.4. LG U+ APPM_TRANS 테이블 덤프

/mnt/hgfs/Desktop/111/oneway_info/APPM_TRANS.txt 파일에는 LG U+ APPM_TRANS 테이블 데이터가 작성되어 있다.

account.txt 파일 내용

caption - account.txt 파일 내용

/work/mnt/hgfs/Desktop/111/ROOT/ROOT/WEB-INF/classes/ibatis/sql/sql-account.xml 파일에서 확인한 APPM_TRANS 테이블 관련 쿼리와 APPM_TRANS.txt에서 확인한 스키마를 비교한 결과 LG U+ APPM_TRANS 테이블 데이터인 것을 알 수 있다.

2.3. 행정안전부 GPKISecureWebX

2.3.1. GPKISecureWebX 소스코드

/work/mnt/hgfs/Desktop/111/GPKISecureWebX 디렉토리 내에는 드림시큐리티에서 개발한 공인인증서 관련 보안 프로그램인 GPKISecureWebX의 소스코드가 존재한다.

Visual Studio Build Log 파일인 /work/mnt/hgfs/Desktop/111/GPKISecureWebX/GPKISecureWebX.plg에서 “01_행자부 웹보안API”를 확인할 수 있고, 행자부는 현재 행정안전부로 불리며 이는 행정안전부 GPKISecureWebX와 관련있는 것을 알 수 있다.

GPKISecureWebX.plg 파일 내용

caption - GPKISecureWebX.plg 파일 내용

추가로 /work/mnt/hgfs/Desktop/111/GPKISecureWebX 디렉토리를 압축한 /work/mnt/hgfs/Desktop/111/1.rar 파일이 존재한다.

2.3.2. GPKISecureWeb 패키지

/work/mnt/hgfs/Desktop/111/gpki.7z 파일 내에 GPKI API, GPKISecureWebX, setup 디렉토리가 존재한다. GPKI 표준 API에 대한 메뉴얼과 여러 키, 인증서 파일이 존재한다.

특히 gpki/gpkisecureweb/log 디렉토리에 2017년 12월부터 2020년 4월까지의 인증서 검증 로그 파일이 존재한다.

로그 파일 일부 데이터

caption - 로그 파일 일부 데이터

2.3.3. GPKISecureWeb 문서 및 모듈 소스코드

/work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수 디렉토리 내에는 인수인계를 위한 GPKISecureWeb 관련 문서 파일과 GPKIInstaller, GPKICertManager 등 전체 모듈에 대한 소스코드가 존재한다.

확인된 문서 파일은 아래와 같다.

  • /work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수/웹용 표준보안API_매뉴얼(유선).chm

  • /work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수/설계문서/SSA_AO_AD_WT_002_웹보안 프로토콜설계서_Ver1.0_.doc

  • /work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수/행자부 웹보안API 인수인계.doc

문서 파일의 속성을 확인하면 오래 전에 제작된 것을 알 수 있다.

doc 파일 문서 속성

caption - doc 파일 문서 속성

/work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수/02_src/ReadMe.txt에 전체 모듈에 대한 설명이 존재한다.

ReadMe.txt 파일 내용

caption - ReadMe.txt 파일 내용

2.4. GPKI 인증서 및 개인키 파일

/work/home/user/Downloads/cert/extracted-key-20200512 디렉토리 내에는 인증서와 개인키 파일이 2475개 존재한다. Subject에서 법제처, 정부산하기관및위원회 등 다양한 정보를 확인할 수 있었지만, 공격자가 이를 어떻게 수집했는지는 알 수 없다.

/work/home/user/Downloads/cert/extracted-key-20200512 디렉토리 파일 목록

caption - /work/home/user/Downloads/cert/extracted-key-20200512 디렉토리 파일 목록

2.5. 온나라 로그인 자동화 스크립트

/work/mnt/hgfs/Desktop/111/onnara_auto 디렉토리 내에는 대한민국 정부에서 사용하는 공무원용 서비스인 온나라 로그인 자동화 스크립트가 존재한다. client_main.py에 온나라 서비스의 계정 id, 조직 id, 서브 도메인이 하드코딩되어 있다.

client_main.py main 함수

caption - client_main.py main 함수

로그인 방식은 입력한 정보를 토대로 /work/mnt/hgfs/Desktop/111/onnara_auto/script 에 존재하는 onnaraSSO.jar 파일을 실행해 SSO 토큰 값을 생성한다.

onnara_sso.py generate_L1 함수

caption - onnara_sso.py generate_L1 함수

onnaraSSO.jar는 입력 옵션에 따라 ARIA 또는 DES 알고리즘으로 문자열을 암·복호화한다. 실행 형식은 아래와 같다.

  • onnaraSSO.jar <문자열> <0:DES | 1:ARIA> <0:암호화 | 1:복호화>

아울러 ARIA와 DES에 사용되는 키는 모두 하드코딩되어 있다.

Host와 VMware 간 옮긴 파일 흔적에서 공격자가 onnara_sso_test.py 파일에 출력 결과를 주석으로 남긴 것을 확인할 수 있다.

/work/home/user/.cache/vmware/drag_and_drop/Z3INst/onnara/onnara_auto/onnara_sso_test.py 파일 데이터

caption - /work/home/user/.cache/vmware/drag_and_drop/Z3INst/onnara/onnara_auto/onnara_sso_test.py 파일 데이터

/work/home/user/.cache/vmware/drag_and_drop/Z3INst/onnara 디렉토리 내에는 onnara9-onnara4-SSO-login.saz 파일이 존재하는데, 압축을 해제하면 실제 HTTP 요청과 응답을 확인할 수 있다.

caption - onnara9-onnara4-SSO-login.saz/raw/001_c.txt 파일 데이터

3. 공격자 및 시스템 정보

본 장에서는 공개된 자료 중 공격자와 시스템 환경에 대하여 분석한 내용을 다룬다.

3.1. 시스템 정보

3.1.1. deepin

VMware workstation에는 deepin이 설치되어 있었다. deepin은 중국 외 국가에서 인기 있는 배포판이 아니고, 중국 기업 주도로 개발되어 중국 시장에 중심으로 배포·지원되는 배포판이다.

독립적으로 검증 가능한 사용자 수나 분포에 대한 데이터는 알려지지 않았지만, deepin은 2022-12에 사용자가 3M 이상이라 보고했고, 2024-09에 "5.4M 이상의 사용자 중 거의 3M이 중국 외 사용자"라고 보고했다. 그러나 외부 검증이 없어 수치의 현실성에는 의문이 있다.

리눅스 사용자들의 특성 상 telemetry, survey 등 데이터 수집에 대한 거부감이 크고, Desktop에서는 상용 소프트웨어에 대한 수요와 시장이 작기 때문에 시장 조사도 활발하지 않다. 따라서 리눅스 배포판 사용자 수에 대한 통계 자료는 얻기 어렵다. 하지만 공개된 자료로 여러 배포판의 대략적인 인기를 알 수 있다. 이 중 각 배포판의 검색 수, Stack Overflow Developer Survey, 그리고 각 배포판의 통계 자료를 정리했다.

| Distribution | Google Trends (Topic, relative to Ubuntu) | Ahrefs Search Traffic | Semrush | Subreddit weekly visitors/members | Stack Overflow (personal/work) | self-reported users |
| --- | --- | --- | --- | --- | --- | --- |
| Ubuntu | 16 | 1.4M-n(ref:https://ahrefstop.com/websites/ubuntu.com) | 3.9M | 251K / 258K | 27.8% / 27.7% | 6M+(2023)-n(ref:https://discourse.ubuntu.com/t/ubuntu-desktop-charting-a-course-for-the-future/38092), 2762K(2017 popcon)-n(ref:https://web.archive.org/web/20171222174904/https://popcon.ubuntu.com/by_inst) |
| Debian | 5 | 297.1K-n(ref:https://ahrefstop.com/websites/debian.org) | 1.1M | 158K / 115K | 11.4% / 10.4% | 265K(2025)-n(ref:https://popcon.debian.org/) |
| Arch Linux | 3 | 342.0K-n(ref:https://ahrefstop.com/websites/archlinux.org) | 1.3M | 271K / 324K | 9.7% / 4.6% |  |
| Linux Mint | 2 | 492.4K-n(ref:https://ahrefstop.com/websites/linuxmint.com) | 1.3M | 172K / 150K |  | 98K(2025)-n(ref:https://blog.linuxmint.com/?p=4811) |
| Fedora | 2 | 222.0K-n(ref:https://ahrefstop.com/websites/fedoraproject.org) | 582.5K | 156K / 142K | 5.8% / 3.7% | 323.3K+(2025)-n(ref:https://discussion.fedoraproject.org/t/fedora-usage-stats/144635/16) |
| Red Hat | 2 | 1.5M-n(ref:https://ahrefstop.com/websites/redhat.com) | 2.5M | 22K / 49K | 1.8% / 5.7% |  |
| CentOS | 1 |  | 208.4K | 3.5K / 16K |  |  |
| NixOS | 1 |  | 142K | 46K / 44K | 3.4% / 1.8% |  |
| openSUSE | <1 |  | 136.7K | 28K / 41K |  | 269K(15.6), 186K(tumbleweed 2025)-n(ref:https://metrics.opensuse.org/d/osrt_access) |
| Gentoo | <1 |  | 27.5K | 22K / 33K |  |  |
| Rocky Linux | <1 |  | 75.7K | 4.2K / 10K |  |  |
| AlmaLinux | <1 |  | 38.1K | 3.7K / 7.3K |  |  |
| deepin | <

위 지표를 종합할 때, deepin의 중국 외 사용자 수가 약 300만 명에 이르지 않는 것으로 보인다. 독립적 검증 부재를 고려하면 수천 명 수준에 그칠 가능성이 상대적으로 높다. 또한 중국 외 지역에서 이 평판은 전반적으로 부정적이다. 또한 아래와 같은 내용들도 알려져있다.

  • 정책, 규제: EULA 및 개인정보 처리방침에 정보 수집 권한과 중화인민공화국 법률 준거가 명시되어 있다.

  • 데이터 수집 논란 이력: 과거 App Store에 CNZZ Analytics를 사용해 논란이 있었고, Deepin은 Google Analytics와 유사하다고 해명 후 수집 중단을 공지하였으나, 이후 Umeng+ Analytics를 다시 추가했다.

  • 보안: deepin desktop environment는 반복된 보안 이슈와 보안 검증을 우회하려는 방식이 지적되었고, 2025-05에 OpenSUSE에서 제거되었다.

종합하면 중국 외 지역에서 deepin을 선택하거나 사용할 확률은 매우 낮은 것으로 판단된다.

3.1.2. IME

VMware workstation에는 여러 중국어용 IME가 설치, 설정된 흔적이 확인되었다. deepin 특성상 중국 시장에 맞춘 기본 구성이 포함되며, 기본 패키지로 fcitx-pinyin, fcitx-sunpinyin, fcitx-table-wubi 가 설치되어 있다. 설치 언어가 영어인 경우 기본 IME는 비활성화되고, 중국어인 경우 wubi, shuangpin,pinyin, sunpinyin이 활성화되는 것으로 확인되었다.

기본 설치된 fcitx 패키지 목록

caption - 기본 설치된 fcitx 패키지 목록

추가 설치 내역은 아래와 같다.

  • fcitx플러그인: huayupy, wbpy, rime, iflyime 추가로 설치 흔적이 확인되었고, 활성화된 입력기는 wbpy, pinyin이다.

  • ibus플러그인: rime 추가 설치 및 사용 흔적이 확인되었다.

  • SogouPY 추가 설정 흔적이 존재한다.

caption - work/home/user/.config/fcitx/profileEnabledIMList diff 결과

3.1.3. etc

설치 및 로케일 정보

work/home/user/.config 하위 설정의 로케일이 영어로 표기되어 있어 영문 환경에서 설치된 것으로 보인다. work/home/user/.config/locale.conf파일 내용은 아래와 같다.

caption - work/home/user/.config/locale.conf 파일 내용

그러나 work/var/log/{access.log*,vmware-network.*.log}파일의 데이터는 한글로 기록되어 있다.

caption - work/var/log/vmware-network.1.log 파일 내용

deepin 설정 변경 및 흔적

work/home/user/.config/deepin/dde-desktop/dde-desktop.conf 파일에 ProtonVPN OpenVPN 프로파일로 보이는 파일이 존재하지만, 덤프에는 포함되어 있지 않다.

work/home/user/.config/deepin/dde-desktop/dde-desktop.conf 파일 내용

caption - work/home/user/.config/deepin/dde-desktop/dde-desktop.conf 파일 내용

work/home/user/.config/deepin/dde-file-manager/dde-file-manager.obtusely.json 파일에 개인 정보를 포함한 파일명과 원격, 공용 경로 접근 이력이 존재한다. 예시는 아래와 같다.

  • standard://downloads/cert/gpki_cert/*

  • file:///mnt/hgfs/share_data/GitHack/www.caa.org.tw/*

  • ftp:host=192.168.50.1:21212

work/home/user/.config/deepin/dcc-weather-plugin.conf파일에서 LocationWaterloo로 설정되어 있다.

work/home/user/.config/deepin/dcc-weather-plugin.conf 파일 내용

caption - work/home/user/.config/deepin/dcc-weather-plugin.conf 파일 내용

이 외에 기타 애플리케이션에서 확인한 흔적은 아래와 같다.

  • work/home/user/.config/filezilla/recentservers.xml: ftp.fu-berlin.de

    • FileZilla를 사용해 공개 FTP 미러에 접속

  • work/home/user/.config/Foxit Software/Foxit Reader.conf: tmp/kclee/hiaei84@gmail.com.pst_pst_*.msg

    • 파일 경로에서 확인한 이메일

3.2. 브라우저 정보

work/home/user 하위 여러 브라우저 프로필을 모두 분석하였고, 주 사용 프로필은 work/home/user/.config/google-chrome/Default로 확인되었다. 전체 브라우저 프로필과 확인된 타임 스탬프 최소, 최대는 아래 표와 같다.

| path | range |
| --- | --- |
| work/home/user/.config/BraveSoftware/Brave-Browser/Default | 2021-05-18 07:15:25.107 ~ 2021-05-18 07:25:22.279 |
| work/home/user/.config/browser/Default | 2021-11-15 17:54:39.645 ~ 2021-11-15 17:55:58.790 |
| work/home/user/.config/chromium/Default | 2019-05-17 05:52:04.363 ~ 2020-12-15 09:21:59.050 |
| work/home/user/.config/google-chrome/Default | 2020-12-02 01:56:37.844 ~ 2025-06-06 04:41:48.697 |
| work/home/user/.mozilla/firefox/06yb5px0.default-release | 2020-12-21T15:59:20.598000 ~ 2021-01-04T17:09:34.052025 |
| work/home/user/.mozilla/firefox/l8g1zjqb.default | 2020-12-21T15:59:20.146000 |
| work/home/user/.thunderbird/3qg2p785.default-default | 2021-04-13T11:39:36.277000 ~ 2021-04-13T12:22:31.051914(추정) |
| work/home/user/.thunderbird/3s68zwb6.default | 2020-12-21T11:44:07.857000 |
| work/home/user/.thunderbird/8inzqqf5.default-release | 2023-12-25T11:47:56.099000 ~ 2023-12-27T10:18:21.103(추정))

work/home/user/.config/google-chrome/Default 프로필 최초 기록은 2020-12-02 01:56:37.844이고, 이후 2025-03-18 04:04:20.873부터 2025-05-28 07:11:01.791까지의 방문 기록이 존재한다. 나머지 프로필은 생성만 되었거나 단기간 사용 흔적에 그쳤다.

3.2.1. language

work/home/user/.config/google-chrome/Default/Preferences의 기록이 work/home/user/.config/google-chrome/Default/.com.google.Chrome.*에 남아 있었다. 아래 표 참고 바란다.

| file name | profile.last_engagement_time |
| --- | --- |
| .com.google.Chrome.yf0Cko | 2020-04-23T10:28:01.605486 |
| .com.google.Chrome.wNxXMb | 2021-04-08T03:15:41.989060 |
| .com.google.Chrome.oM0Y0k | 2021-05-18T07:46:52.497051 |
| .com.google.Chrome.qTDg5z | 2025-04-10T16:13:27.859136 |
| .com.google.Chrome.m6662X | 2025-05-20T00:26:05.876747 |
| .com.google.Chrome.LajSSi | 2025-05-22T00:31:18.814059 |
| Preferences | 2025-05-28T07:11:01.793570

변경 타임라인은 아래와 같다.

  • 2020-04-23T10:28:01.605486 이전: intl.*_languages에 영어+중국어 설정, 중국어 번역 차단(translate_blocked_languages).

  • 2021-04-08T03:15:41.989060 이전: 한국어 추가, 한국어 번역 차단.

  • 2025-04-10T16:13:27.859136 이전: 영어 번역 차단 추가.

번역 수락은 영어와 한국어에 주로 발생하였고, 중국어는 수락 이력이 없다. 번역 결과 언어는 중국어로 기록하였다.

주로 방문한 페이지는 영어, 중국어 페이지이고 2021-04-08까지 한글 사이트에 방문하였다. 2025-04-10 이후에는 한글 사이트 방문이 거의 없다.

3.2.2. history

work/home/user/.config/google-chrome/Default 프로필은 업무와 업무 외 용도로 사용되었다. 취약점, 악성코드 등 업무와 관련된 기록 외에도 포럼 등을 이용한 기록이 존재한다.

Cobalt Strike 관련 방문 기록은 아래 표와 같다.

| first visit | url | title |
| --- | --- | --- |
| 2025-03-24 05:25:47.145 | https://github.com/TH3xACE/EDR-Test | GitHub - TH3xACE/EDR-Test: Automating EDR Testing with reference to MITRE ATTACK via Cobalt Strike [Purple Team]. |
| 2025-03-25 08:37:57.366 | https://github.com/georgesotiriadis/Chimera | GitHub - georgesotiriadis/Chimera: Automated DLL Sideloading Tool With EDR Evasion Capabilities |
| 2025-03-26 05:17:51.538 | https://github.com/kyleavery/AceLdr | GitHub - kyleavery/AceLdr: Cobalt Strike UDRL for memory scanner evasion. |
| 2025-04-07 01:13:41.868 | https://github.com/tomcarver16/BOF-DLL-Inject | GitHub - tomcarver16/BOF-DLL-Inject: Manual Map DLL injection implemented with Cobalt Strike's Beacon Object Files. |
| 2025-04-10 01:49:21.313 | https://github.com/Cobalt-Strike/ProxyDLLExample | GitHub - Cobalt-Strike/ProxyDLLExample: code for the Proxy DLL example blog post |
| 2025-04-16 04:49:48.157 | https://github.com/n3k7ar91/Cobalt-Strike-EDR-AV-bypass | GitHub - n3k7ar91/Cobalt-Strike-EDR-AV-bypass: Various resources to enhance Cobalt Strike's functionality and its ability to evade antivirus/EDR detection |
| 2025-04-29 02:04:12.124 | https://github.com/CrackerCat/CobaltStrike-KunKun | GitHub - CrackerCat/CobaltStrike-KunKun: 坤坤 CS 基于 CobaltStrike cat 4.5 二开项目 添加反沙箱、反测绘。集成常用后渗透插件,开箱即用(GitHub - CrackerCat/CobaltStrike-KunKun: KunKun CS is a CobaltStrike cat 4.5 based project that adds anti-sandbox and anti-mapping capabilities. It also integrates common post-exploitation plugins and is ready to use.) |
| 2025-04-29 02:04:46.756 | https://github.com/D13Xian/CobaltStrike-KunKun | GitHub - D13Xian/CobaltStrike-KunKun: 坤坤 CS 基于 CobaltStrike cat 4.5 二开项目 添加反沙箱、反测绘。集成常用后渗透插件,开箱即用(GitHub - D13Xian/CobaltStrike-KunKun: KunKun CS is a CobaltStrike cat 4.5 based project that adds anti-sandbox and anti-mapping capabilities. It also integrates common post-exploitation plugins and is ready to use.) |
| 2025-05-27 01:14:48.105 | https://github.com/RedefiningReality/Cobalt-Strike | GitHub - RedefiningReality/Cobalt-Strike: Various resources to enhance Cobalt Strike's functionality and its ability to evade antivirus/EDR detection |

rootkit 관련 방문 기록은 아래 표와 같다.

| first visit | url | title |
| --- | --- | --- |
| 2025-03-24 00:57:27.819 | https://github.com/D4stiny/spectre | GitHub - D4stiny/spectre: A Windows kernel-mode rootkit that abuses legitimate communication channels to control a machine. |
| 2025-03-24 00:57:30.161 | https://github.com/TheMalwareGuardian/Bentico | GitHub - TheMalwareGuardian/Bentico: Windows Kernel Mode Rootkit |
| 2025-03-24 00:57:33.816 | https://github.com/eversinc33/Banshee | GitHub - eversinc33/Banshee: Experimental Windows x64 Kernel Rootkit with anti-rootkit evasion features. |
| 2025-03-24 00:57:35.016 | https://github.com/alal4465/Win_Rootkit | GitHub - alal4465/Win_Rootkit: A kernel-mode rootkit with remote control |
| 2025-03-24 01:27:30.574 | https://github.com/Nullxyzz/Windows-RootKit | GitHub - Nullxyzz/Windows-RootKit: Its not made by me, Its just a compilation of diffrent windows rootkits |
| 2025-03-24 01:39:51.900 | https://github.com/brosck/Frosty | GitHub - brosck/Frosty: 「🧊」Ring 3 Rootkit for Windows 10 |
| 2025-03-24 01:39:51.900 | https://github.com/MrEmpy/Frosty | GitHub - brosck/Frosty: 「🧊」Ring 3 Rootkit for Windows 10 |
| 2025-03-24 01:40:05.815 | https://github.com/Dol3v/Mark | GitHub - Dol3v/Mark: Windows kernel rootkit for the highschool's cyber track |
| 2025-03-24 01:40:07.609 | https://github.com/memN0ps/eagle-rs | GitHub - memN0ps/eagle-rs: Rusty Rootkit - Windows Kernel Rookit in Rust (Codename: Eagle) |
| 2025-03-24 01:40:32.416 | https://github.com/eLoopWoo/zwhawk | GitHub - eLoopWoo/zwhawk: A kernel rootkit with remote command and control interface for windows |
| 2025-03-24 01:46:34.018 | https://github.com/rakendrathapa/NetRootKit | GitHub - rakendrathapa/NetRootKit: Hide the TCP Connection |
| 2025-03-25 09:13:08.826 | https://github.com/Meowoverflow/Rootkits_Subverting_the_Windows_Kernel | GitHub - Meowoverflow/Rootkits_Subverting_the_Windows_Kernel: source code for the examples and topics from the book |
| 2025-03-25 09:16:46.105 | https://github.com/landhb/HideProcess | HideProcess,一个基本的直接内核对象操作 rootkit,它从 EPROCESS 列表中删除一个进程,并将其从任务管理器中隐藏起来(HideProcess, a basic direct kernel object manipulation rootkit that removes a process from the EPROCESS list and hides it from Task Manager) |
| 2025-04-03 02:36:21.421 | https://github.com/carloslack/KoviD | GitHub - carloslack/KoviD: Red-Team Linux kernel rootkit |
| 2025-04-03 02:48:13.341 | https://github.com/FunnyWolf | FunnyWolf (rootkit) · GitHub |
| 2025-04-10 04:37:45.062 | https://github.com/BeneficialCode/WinArk | GitHub - BeneficialCode/WinArk: Windows Anti-Rootkit Tool |
| 2025-04-10 05:10:11.795 | https://github.com/swwwolf/wdbgark | GitHub - swwwolf/wdbgark: WinDBG Anti-RootKit Extension |
| 2025-04-10 05:10:15.137 | https://github.com/eternalklaus/EternalAntirootkit | GitHub - eternalklaus/EternalAntirootkit: Anti-rootkit works as a Windows system driver. |
| 2025-04-10 05:10:43.093 | https://github.com/forentfraps/AntiRootkit | GitHub - forentfraps/AntiRootkit: Usermode rootkit scanner written in C x86 + x86_64 |
| 2025-04-10 06:45:19.255 | https://github.com/codetronik/KernelV | KernelV,Rootkit 和 Anti-rootkit,下载 KernelV 的源码*GitHub*帮酷 |
| 2025-04-17 01:03:23.699 | https://github.com/XaFF-XaFF/Black-Angel-Rootkit | GitHub - XaFF-XaFF/Black-Angel-Rootkit: Black Angel is a Windows 11/10 x64 kernel mode rootkit. Rootkit can be loaded with enabled DSE while maintaining its full functionality. |
| 2025-04-17 01:03:46.823 | https://github.com/XaFF-XaFF/Cronos-Rootkit | GitHub - XaFF-XaFF/Cronos-Rootkit: Cronos is Windows 10/11 x64 ring 0 rootkit. Cronos is able to hide processes, protect and elevate them with token manipulation. |
| 2025-04-17 01:08:20.491 | https://github.com/Idov31/Nidhogg | GitHub - Idov31/Nidhogg: Nidhogg is an all-in-one simple to use windows kernel rootkit. |
| 2025-04-17 01:08:22.483 | https://github.com/ColeHouston/Sunder | GitHub - ColeHouston/Sunder: Windows rootkit designed to work with BYOVD exploits |
| 2025-04-17 01:08:24.541 | https://github.com/ispoleet/malware | GitHub - ispoleet/malware: Some of my old malware |
| 2025-04-17 01:10:21.217 | https://github.com/joaoviictorti/shadow-rs | GitHub - joaoviictorti/shadow-rs: Windows Kernel Rootkit in Rust |
| 2025-04-17 01:14:23.331 | https://github.com/ZeroMemoryEx/Chaos-Rootkit | GitHub - ZeroMemoryEx/Chaos-Rootkit: Now You See Me, Now You Don't |
| 2025-04-17 01:14:28.746 | https://github.com/crvvdev/MasterHide | GitHub - crvvdev/MasterHide: A x64 Windows Rootkit using SSDT or Hypervisor hook |
| 2025-04-17 01:14:40.074 | https://github.com/ZeroMemoryEx/URootkit | URootkit,简单的用户模式 Rootkit,下载 URootkit 的源码*GitHub*帮酷(GitHub - kumar/URootkit: Simple user-mode rootkit. Download the source code for URootkit.) |
| 2025-04-17 01:14:58.455 | https://github.com/assarbad/Nidhogg | GitHub - assarbad/Nidhogg: Nidhogg is an all-in-one simple to use rootkit for red teams. |
| 2025-04-17 01:15:13.143 | https://github.com/adamhlt/Basic-Rootkit | GitHub - adamhlt/Basic-Rootkit: POC Ring3 Windows Rootkit (x86 / x64) - Hide processes and files |
| 2025-04-17 01:15:17.263 | https://github.com/huoji120/numen | GitHub - huoji120/numen: 简单安排一下 autochk.sys 这个 rootkit |
| 2025-04-27 05:33:00.318 | https://github.com/NoviceLive/research-rootkit | GitHub - NoviceLive/research-rootkit: LibZeroEvil & the Research Rootkit project. |
| 2025-04-27 05:40:00.848 | https://github.com/T1erno/ALPART | GitHub - T1erno/ALPART: Automatic Linux Persistence And Rootkit Tool |
| 2025-04-27 05:40:02.549 | https://github.com/MatthiasCr/LKM-Rootkit | GitHub - MatthiasCr/LKM-Rootkit: Malware to gain persistence on a victims machine. |
| 2025-04-27 05:45:31.565 | https://github.com/pythonmandev/rootkit_persistence- | rootkit*persistence-,专注于 Linux rootkit 的代码集合,下载 rootkit_persistence-的源码\_GitHub*帮酷(GitHub - rootkit_persistence/rootkit_persistence-: A collection of code focused on Linux rootkits. Download the source code.) |
| 2025-04-27 05:45:34.469 | https://github.com/DualHorizon/blackpill | GitHub - DualHorizon/blackpill: A Linux kernel rootkit in Rust using a custom made type-2 hypervisor, eBPF XDP and TC programs |
| 2025-04-27 05:51:12.876 | https://github.com/MatheuZSecurity/Rootkit | Rootkit,专注于 Linux rootkit 的代码集合,下载 Rootkit 的源码*GitHub*帮酷 |
| 2025-04-27 05:54:57.312 | https://github.com/killvxk/Rootkit-MatheuZSecurity | GitHub - killvxk/Rootkit-MatheuZSecurity: Collection of codes focused on Linux rootkits |
| 2025-05-20 05:11:12.591 | https://github.com/f0rb1dd3n/Reptile | GitHub - f0rb1dd3n/Reptile: LKM Linux rootkit |

translate.google.* 방문 기록에서 주로 영어, 한글 문서, docs, LLM 응답 등을 중국어로 번역했다. 번역 내용 중 일부는 아래와 같다.

History에서 확인한 Google Translate

caption - History에서 확인한 Google Translate

| timestamp | text |
| --- | --- |
| 2025-03-27 04:52:46.942 | 문서는 대한민국의 안전보장상 중요한      내용과 개인정보가 수록되어 있으므로 아래      \\n     사항을 준수해야  |
| 2025-04-03 00:54:16.358 | CryptoMalloc\\nEncrypt your RAM!!! Allows you to encrypt the "physical" memory of any process.\\n\\nHow does it work?\\nCryptoMalloc as the name suggest overloads the libc standard malloc function and replaces it with the one that will map process\\' virtual memory to a managed ANONYMOUS mapping. ... |
| 2025-04-22 08:35:59.740 | 무선용 비대칭키 암호 메시지를 처리한다.\\nParameters:\\nbaCert - 키분배용 인증서\\nbaPriKey - 키분배용 개인키\\nbaWapEnvData - 무선용 비대칭키 암호 멧시지\\nReturns:\\n성공시 0 리턴 (baReturnArray를 이용하여 원본 메시지를 획득한다.)\\n실패시 에러코드(양수) 리턴 (sReturnString, sDetailErrorString을 이용하여 에러 원인을 확인한다.) |
| 2025-04-25 06:09:41.951 | // They didn\\'t like posix basename so they defined another function with the\\n// same name and if you include libgen.h it #defines basename to something\\n// else (where they implemented the real basename), and that define breaks\\n// the table entry for the basename command. They didn\\'t make a new function\\n// with a different name for their new behavior because gnu.\\n//\\n// Solution: don\\'t use their broken header, provide an inline to redirect the\\n// correct name to the broken name.\\n |
| 2025-05-07 07:24:38.343 | \\nSVIP用户使用地址点击我\\nlibev dup2 in thread\\n欢迎使用 公益站! 站长合作邮箱:wxgpt@qq.com\\n\\nOkay, let\\'s break down the implications of using dup2 in a thread while libev is running, especially if the file descriptors involved are being managed by libev.\\n\\nThe Core Problem:\\n\\nUsing dup2 on file descriptors (FDs) that are currently being watched by a libev event loop, ... |

방문 기록 중에 특히 중국 포럼인 "acfun.cn/v/list63/index.htm"를 자주 방문하였다. acfun.cn 첫 기록은 2021-05-19 03:08:57.000이고, 방문 기록이 존재하는 2025-03-18 08:00:42.949~2025-05-28 07:11:01.791 사이에 232번, 전체 acfun.cn 방문 기록은 1,440번이다.

검색 엔진 사용 빈도는 아래 표와 같다. google, duckduckgo 다음으로 baidu를 가장 많이 사용한 것을 알 수 있다.

| search engine | count |
| --- | --- |
| google.com/search | 7,332 |
| duckduckgo.com | 1,301 |
| baidu.com | 319 |
| yandex.com | 189

3.2.3. geolocation

work/home/user/.config/google-chrome/Local State의 기록이 work/home/user/.config/google-chrome/.com.google.Chrome.*에 남아 있었다. 이 기록들에 따르면 Chrome VariationsService가 위치를 tw, hk, jk, sg 순서로 바뀌었다. 자세한 정보는 아래 표 참고 바라며, variations_permanent_consistency_country, variations_country는 소스코드를 분석해 기능을 확인하였다.

  • variations_permanent_consistency_country: https://source.chromium.org/chromium/chromium/src/+/main:components/variations/pref_names.cc;l=59

  • variations_country: https://source.chromium.org/chromium/chromium/src/+/main:components/variations/pref_names.cc;l=22

| file name | profile.info_cache.Default.active_time | variations_permanent_consistency_country | variations_country |
| --- | --- | --- | --- |
| .com.google.Chrome.zDDdld | 2020-04-23T19:11:59.112960 | tw | tw |
| .com.google.Chrome.a24195 | 2021-04-08T12:12:44.552911 | hk | jp |
| .com.google.Chrome.JqmFGk | 2021-05-18T15:50:41.894885 | jp | jp |
| Local State(Brave) | 2021-05-18T16:13:29.503903 | JP | JP |
| .com.google.Chrome.6G7Z9m | 2025-04-18T10:32:30.958497 | sg | sg |
| .com.google.Chrome.iNphSA | 2025-05-20T09:25:28.242808 | sg | sg |
| Local State | 2025-05-28T15:48:54.071609 | sg | sg

work/home/user/.mozilla/firefox/06yb5px0.default-release/saved-telemetry-pings/ 아래의 event ping들의 검색 엔진 지역 설정은 모두 HK이다.

| file name | creationDate | environment.settings.userPrefs.browser.search.region |
| --- | --- | --- |
| 07c6c260-34a1-4c05-bc9c-881f7c0ee5bc | 2021-01-04T10:44:37.490Z | HK |
| 3c594245-10b0-40a4-b968-0eec2d771c94 | 2021-01-04T11:44:37.491Z | HK

work/home/user/.thunderbird/*/.default-default/saved-telemetry-pings 디렉토리 내에 존재하는 파일에서 payload.info.timezoneOffset를 확인할 수 있다.

| profile directory name | file name | creationDate | payload.info.timezoneOffset |
| --- | --- | --- | --- |
| 3qg2p785.default-default | eb9839a3-f01f-442b-b25e-a7392c658222 | 2021-04-13T16:00:00.031Z | 480(UTC+8) |
| 3qg2p785.default-default | 0fc51ffd-81a2-4cf0-8c8b-faba548e05ce | 2021-04-14T01:26:25.515Z | 480(UTC+8) |
| 3qg2p785.default-default | 67d8e780-b911-4a9a-9eb7-8b4090252a50 | 2021-04-14T01:26:25.531Z | 480(UTC+8) |
| 3qg2p785.default-default | d5de4158-1043-4b29-8b09-18ce575719be | 2021-04-14T01:26:25.533Z | 480(UTC+8) |
| 3qg2p785.default-default | 3a5102ff-1111-493b-9ab3-2189ad6edc78 | 2022-03-30T22:22:15.828Z | 480(UTC+8) |
| 3qg2p785.default-default | 075cdd5b-1ac0-4dd1-b5bb-f887d8d5db47 | 2022-03-30T22:22:15.829Z | 480(UTC+8) |
| 3qg2p785.default-default | bae0b2e3-3dec-4015-986d-e8d7d97e9ac7 | 2022-03-30T22:22:19.397Z | 480(UTC+8) |
| 3qg2p785.default-default | 91500b5d-b577-45f5-8ec8-5d9b0a5cd1bc | 2022-03-30T22:22:19.398Z | 480(UTC+8) |
| 8inzqqf5.default-release | 1e69b935-10ca-4643-9e15-5b85d23aac29 | 2023-12-27T10:18:21.103Z | 540(UTC+9) |
| 8inzqqf5.default-release  | 380a9109-2b49-486b-ad0f-23fe852f6bef | 2023-12-27T10:18:21.101Z | 540(UTC+9)


work/home/user/.thunderbird/*/.default-default/saved-telemetry-pings 디렉토리 내 times.json, prefs.js 파일에서 각각 firstUse와 calendar.timezone.local을 확인하였다.

| profile directory name | firstUse | calendar.timezone.local  |
| --- | --- | --- |
| 3qg2p785.default-default | 2021-04-13T11:39:36.277000 | Asia/Shanghai |
| 8inzqqf5.default-release | 2023-12-25T11:47:56.099000 | Asia/Seoul

브라우저 방문 기록에서 Google의 지역 탐지 결과들이 위 데이터를 뒷받침한다.

  • 2020-12-21T16:07:17.035527에 google.com title이 Google 搜尋이다.

    • work/home/user/.mozilla/firefox/06yb5px0.default-release

  • 2021-05-18 07:15:25.120에 google.com title이 Google 検索이다.

    • work/home/user/.config/BraveSoftware/Brave-Browser/Default

  • 2021-05-18 07:15:30.999에 google.com에서 "translate"를 검색한 뒤, 검색 결과를 통해 translate.google.co.jp로 접속했다.

    • work/home/user/.config/BraveSoftware/Brave-Browser/Default

  • 2025-03-19 01:15:18.988에 google.com title이 Google 搜索이다.

    • work/home/user/.config/google-chrome/Default

  • 2025-03-27 04:52:26.132에 google.com에서 "translate"를 검색한 뒤, 검색 결과를 통해 translate.google.com.sg에 접속했다.

    • work/home/user/.config/google-chrome/Default

  • 2025-05-13 05:08:47.578에 google.com에서 "google search"를 검색한 뒤, 검색 결과를 통해 google.com.sg에 접속했다.

    • work/home/user/.config/google-chrome/Default

또한 github.com title도 중국어로 localize된 경우가 많았다.

3.2.4. etc

work/home/user/.config/baidunetdisk 경로에 BaiduNetdisk 클라이언트 설정 파일들이 존재한다. BaiduNetdisk는 바이두 클라우드(중국계 클라우드) 스토리지/동기화 클라이언트이다.

baidunetdisk 디렉토리

caption - baidunetdisk 디렉토리

work/home/user/.thunderbird/8inzqqf5.default-release/encrypted-openpgp-passphrase.txt 파일이 존재하는 것을 보아 공격자는 해당 프로필에서 openpgp를 사용하였다.

work/home/user/.config/google-chrome/Default/Local Storageamiunique.org 정보가 저장된 것을 알 수 있는데, amiunique.org은 browser fingerprint의 통계적 빈도를 확인할 수 있는 사이트이다. 이 정보를 확인할 때 확인된 정보가 저장된다.

{
  "value": {
    "fpHashed": "05b8bbd2fdc2823aceba4857bba8d604b57decce",
    "cookieId": "24c16ac1-9407-4801-8a56-5381af2ec10f",
    "attributes": {
      "0": [
        {
          "attribute": "userAgent-js",
          "value": "Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6301.219 Safari/537.36",
          "cpt": 32,
          "ratio": 8.588111745289488e-6,
          "versions": {}
        },
        {
          "attribute": "platform",
          "value": "Win32",
          "cpt": 1381816,
          "ratio": 0.3708496943571543,
          "versions": {}
        },
        // ...
        {
          "attribute": "timezone",
          "value": "-540",
          "cpt": 27431,
          "ratio": 0.007361890415157373,
          "versions": {}
        },
        {
          "attribute": "languages-js",
          "value": "en-US,en,ko,zh-CN",
          "cpt": 6,
          "ratio": 1.610270952241779e-6,
          "versions": {}
        },
        // ...
        {
          "attribute": "screen_width",
          "value": "1920",
          "cpt": 760879,
          "ratio": 0.20420355864512876,
          "versions": {}
        },
        {
          "attribute": "screen_height",
          "value": "1200",
          "cpt": 50011,
          "ratio": 0.013421876765427268,
          "versions": {}
        },
        {
          "attribute": "screen_depth",
          "value": "24",
          "cpt": 3124471,
          "ratio": 0.8385408154036372,
          "versions": {}
        },
        {
          "attribute": "screen_availTop",
          "value": "0",
          "cpt": 2994567,
          "ratio": 0.8036773757736345,
          "versions": {}
        },
        {
          "attribute": "screen_availLeft",
          "value": "41",
          "cpt": 191,
          "ratio": 5.126029197969663e-5,
          "versions": {}
        },
        {
          "attribute": "screen_availHeight",
          "value": "1200",
          "cpt": 15086,
          "ratio": 0.004048757930919913,
          "versions": {}
        },
        {
          "attribute": "screen_availWidth",
          "value": "1879",
          "cpt": 312,
          "ratio": 8.373408951657251e-5,
          "versions": {}
        },
        // ...
        {
          "attribute": "battery",
          "value": { "charging": "true", "chargingTime": "0", "level": "1" },
          "cpt": 576035,
          "ratio": 0.15459540466243218,
          "versions": {}
        },
        // ...
        {
          "attribute": "x-forwarded-proto",
          "value": "https",
          "cpt": 3717236,
          "ratio": 0.9976261922379036,
          "versions": {}
        },
        {
          "attribute": "x-real-ip",
          "value": "156.59.13.154",
          "cpt": 1,
          "ratio": 2.683784920402965e-7,
          "versions": {}
        },
        {
          "attribute": "x-forwarded-for",
          "value": "156.59.13.154",
          "cpt": 1,
          "ratio": 2.683784920402965e-7,
          "versions": {}
        },
        // ...
        {
          "attribute": "client-ip",
          "value": "156.59.13.154",
          "cpt": 1,
          "ratio": 2.683784920402965e-7,
          "versions": {}
        }
      ]
      // ...
    }
    // ...
  },
  "expiry"

work/home/user/.thunderbird/8inzqqf5.default-release/session.json에 열려 있는 windows, tabs 정보가 기록되어 있다. 파일 경로들을 해석한 결과는 아래와 같다.

  • tos安装文件: tos installation file

  • 이메일 파일 이름 형식은 maildir mailbox format으로 보이고, S=는 파일의 크기를 나타낸다.


모든 정보를 종합하면 다음과 같다.

  1. 공격자는 일반적으로 중국 외 지역에서 사용하지 않는 deepin을 사용한다.

  2. IME 설치 흔적에서 여러 중국어용 IME만 설치된 것을 확인하였다.

  3. 브라우저 방문 기록에서 중국 포럼을 자주 방문한 것을 확인하였다.

  4. 번역 기록에서 한국어 -> 중국어, 영어 -> 중국어로 번역한 것을 확인하였다. 중국어에서 다른 나라 언어로 번역한 것은 존재하지 않는다.

시스템 정보에서 일부 한국어가 확인되었지만 한국인으로 보이지 않고, 공격자는 중국어에 능숙하며 중국 생태계(서비스·소프트웨어)에 익숙한 것으로 보인다.

4. 과거 국내 침해사고 연관성

4.1. 침해사고 개요

당사는 2022년 국내 금융회사 침해사고 조사를 수행하였다. 사건은 대출 신청 과정에서 고객 정보 입력 후 며칠 내로 타 금융사에서 영업성 연락이 왔고, 이를 이상 징후로 인지한 고객사는 당사에 침해사고 조사를 의뢰하였다.

초기 서버 포렌식 과정에서 악성코드 실행 파일은 확인되지 않았으나, 네트워크 패킷 덤프에서 SMTP(25/tcp) 세션의 페이로드에서, HTTP 헤더·바디 데이터와 암호화된 데이터가 함께 포함된 패킷이 확인되었다. 패턴 기반 키 추정을 통해 암호화 키를 알아내어 복호화에 성공하였고 그 결과 악성코드 실행 명령어를 확인할 수 있었다. 실제로 복호화된 데이터는 아래와 같다.

  • /etc/[악성코드 경로]/[인포스틸러 로더] -jar /etc/[악성코드 경로]/[인포스틸러] -url [DB 정보] -u [계정 아이디] -p [계정 비밀번호] -q[날짜 및 시간 데이터] -qSTA MBDE -d "0:0, 1:1, 2:1, 5:0" -de "0"

    exit

악성코드 경로에서 인포스틸러와 백도어를 확인하였으며, 이후 디스크 이미지 분석으로 루트킷 존재를 확인하였다.

4.2. 악성코드 연관성

공격에 사용된 인포스틸러는 공격자가 원하는 정보 탈취에 맞춰 맞춤 제작되었으며, 백도어와 루트킷은 구성 및 동작 방식이 공격자 VMware 덤프 파일 내 tomcat20220420_rootkit 디렉토리에서 확인된 백도어, 루트킷과 일치하다.

좌) master.c(백도어) main 함수 소스코드, 우) 침해사고 당시 확보한 백도어 main 함수 의사코드

caption - 좌) master.c(백도어) main 함수 소스코드, 우) 침해사고 당시 확보한 백도어 main 함수 의사코드

좌) main.c(루트킷) init 함수 소스코드, 우) 침해사고 당시 확보한 루트킷 init_module 함수 의사코드

caption - 좌) main.c(루트킷) init 함수 소스코드, 우) 침해사고 당시 확보한 루트킷 init_module 함수 의사코드

빌드할 때 config.sh 파일에 따라 결정되는 매직 패킷, 루트킷 이름, 백도어 이름 등은 상이하지만 통신 데이터 xor key, AES key, iv 등 다양한 변수 값이 동일하다.

4.3. 공격 배후 근거

당시에는 백도어와 루트킷에 대하여 공개된 정보가 없었고, 루트킷이 adore-ng를 참고하여 제작되었다는 점 외에는 특정되지 않았다. 또한 코드 유사도, 공격 방식만으로 특정 행위자 귀속에는 한계가 있었다. 그러나 “목적·전술(TTP)”를 토대로 북한 APT 그룹은 아닌 것으로 결론을 내렸다. 주요 근거는 아래와 같다.

  • 북한 APT 그룹은 주로 정보 수집이나 자산 탈취 성향이 강한 반면, 본 사건은 외부 서버에서 내부 서버까지 침투하여 DB 서버의 고객 정보를 탈취하고 보이스피싱에 활용된 정황이 중심이었다.

  • 최초 침투 역시 스피어 피싱이나 사회 공학적 공격 기법이 아닌, 웹 서비스 취약점을 이용하였다. 이는 북한 APT 그룹이 주로 사용하는 초기 침투 방식과 다르다. 또한 웹 서비스 취약점을 이용하여 점거하더라도, C&C 서버로 활용하며 내부 침투까지 하는 경우는 드물다.

  • 공개된 정보 중, 리눅스 시스템 대상으로 북한 APT 그룹이 루트킷과 백도어를 공격에 활용한 이력은 존재하지 않는다.

이후 2022년 6월, 루트킷을 avast에서 syslogk로 명명하여 보고서를 공개하였지만 야생에서 확인된 것으로 밝혔으며 여전히 공격 배후에 대한 근거가 부족하였다.

그러나 이번에 공개된 공격자 VMware 덤프에서 백도어와 루트킷의 원본 소스코드가 확인되었으며, 이는 공격 배후를 추정할 수 있는 유력한 근거로 판단된다. 또한 디렉토리 명도 침해사고 발생 시점과 유사하다.

따라서 APT Down - The North Korea Files 보고서에서 언급된 공격자가 2022년 국내 금융회사 침해사고 공격 배후로 유력하다고 판단된다.

마지막으로 백도어와 루트킷은 2025년 버전이 존재하는 것을 보아, 해당 악성코드들은 2022년부터 공격자가 지속적으로 사용하였고 2022년 국내 금융회사 공격 사례 외에도 공격자는 추가 공격을 수행해왔을 가능성이 높다고 생각된다. 이에 따라 감염 여부 점검 방법을 "7. syslogk rootkit 감염 여부 점검 방법"에서 설명한다.

5. 공격 배후 추정 및 근거

APT Down - The North Korea Files 보고서의 공격 배후는 중국 UNC5221 그룹과 연관있을 것으로 추정된다. 판단 근거는 아래에서 자세히 설명한다.

5.1. Kimsuky

5.1.1. Operation Covert Stalker

APT Down - The North Korea Files 보고서에 Operation Covert Stalker와 연관된 웹 서버 설정(SSLCertificateFile /etc/ssl/certs/apache-selfsigned.crt)이 특정 파일에서 확인되었다는 언급이 존재한다. 다만 내용이 축약되어 확실한 정보는 없었고, 안랩 Operation Covert Stalker 보고서에서 동일 지표를 확인할 수 없어 근거가 미약하다고 판단하였다.

5.1.2. GPKI Stolen Certificates

공격자 VMware 덤프 파일에서 GPKI 인증서가 다수 확인되었다. Kimsuky의 Troll Stealer가 GPKI 인증서 탈취 기능이 존재하는 것은 알려져 있으나, 공격자가 GPKI 인증서를 어떻게 탈취하였는지 확인되지 않았고 Troll Stealer와 연관된 파일이 발견되지 않았다. 따라서 Kimsuky와 연관성을 판단하기에는 근거가 미약한 것으로 판단된다.

5.1.3. Similar Targets

공격자 VMware 덤프 파일 중 네이버 피싱과 관련있는 파일(/work/mnt/hgfs/Desktop/New folder/htdocs/generator.php)에서 nid[.]navermails[.]com 도메인이 확인되었다. 해당 도메인은 안랩에서 작성한 보고서에서 네이버 피싱 도메인으로 언급되지만 Kimsuky에 대한 내용은 찾을 수 없다. 따라서 근거가 미약하다고 판단하였다.

5.2. APT41 & UNC3886

5.2.1. reptile rootkit 연관성

reptile rootkit은 APT41과 UNC3886 그룹이 공격에 사용한 이력이 보고되었다.

공격자 VMware 덤프 파일에서 확인된 syslogk(tomcat*_rootkit)과 reptile rootkit의 공통점은 다음과 같다.

  1. 함수 후킹을 위해 사용하는 라이브러리가 동일하다.

  2. 일부 함수가 유사한 코드를 가진다.

  3. port knocking 방식으로 백도어를 실행한다.

syslogk와 reptile rootkit의 연관성이 존재하지만, reptile rootkit은 오픈소스이고 해당 지표만으로 특정 APT와의 연관성을 판단하기에는 무리가 있다고 판단하였다. 실제로 syslogk는 reptile rootkit 외에 KoviD rootkit 소스코드가 그대로 사용되는 등 다양한 오픈소스를 참고한 흔적이 존재한다. 따라서 APT41과 Unc3886으로 귀속하기에는 근거가 미약한 것으로 판단된다.

5.2.3. TinyShell 연관성

UNC3886 그룹이 공격에 자주 사용하는 것으로 알려진 TinyShell 소스코드가 /work/mnt/hgfs/share_data/backdoor/20220812/SSS 디렉토리에 존재한다. 그러나 구글에서 보고된 UNC3886의 TinyShell기반의 백도어는 AES, HMAC 또는 RC4로 암호화된 통신을 하는데, 확인된 TinyShell 소스코드는 AES와 SHA1으로 암호화된 통신을 한다. 통신 방법에 차이가 있어 근거가 미약한 것으로 판단하였다.

5.3. UNC5221

5.3.1. CVE-2025-0282, BrushFire

CVE-2025-0282는 Ivanti Connect Secure의 RCE 취약점으로, UNC5221이 악용하여 악성코드를 유포한 정황이 보고되었다. 공격자 파일 중 해당 취약점을 악용해 악성코드를 배포하는 Python 스크립트 6개가 확인되었으며, 스크립트에서 유포되는 악성코드는 UNC5221 공격에서 확인된 BrushFire로 식별되었다.

동일한 취약점 악용과 동일 악성코드 유포 정황을 근거로 UNC5221과 연관성이 높다고 판단하였다.

5.3.2. SPAWN Family

SPAWN Family는 UNC5221이 자주 사용하는 악성코드로, /work/mnt/hgfs/Desktop/New folder/203.234.192.200_client.zip 파일 내에서 SPAWN Family와의 관련 지표가 확인되었다. 연관성이 확인된 SPAWN Family는 SPAWNMOLE, SPAWNSNAIL, SPAWNCHIMERA이다.

client.py에 정의된 client_hello 값은 이후 코드에서 자동 변경되는데, 초기 값은 UNC5221 공격에서 사용된SPAWNMOLE의 매직 패킷과 일치한다. 변경된 client_hello 값은 JPCERT 블로그에서 보고된 SpawnChimera 매직 패킷 설명과 유사한 것으로 확인되었다. SPAWNCHIMERA 악성코드는 SPAWNMOLE과 마찬가지로 UNC5221의 공격에 사용된 악성코드로, 앞서 언급된 CVE-2025-0282 취약점을 통해 유포된 악성코드이다.

controller.py는 client.py가 구동한 SOCKS5 프록시를 통해 SSH 서버에 접속하는 클라이언트 스크립트로, SPAWNSNAIL 혹은 SPAWNCHIMERA의 클라이언트 프로그램으로 추정된다.

따라서 SPAWN Family 계열 악성코드가 다수 확인되었고, client_hello 초기 값과 변경 값이 각각 SPAWNMOLE, SPAWNCHIMERA 매직 패킷 특성과 일치하고 유사한 점, SPAWN Family 계열 악성코드가 UNC5221의 대표적인 악성코드인 점을 근거로 UNC5221과 연관성이 높다고 판단하였다.

5.3.3. ROOTROT

ROOTROT은 UNC5221의 공격에서 확인된 웹쉘이다. Perl로 작성되어 있으며, 쿠키의 특정 값을 base64로 디코딩하여 eval로 실행한다. 실행 결과는 HTML 주석 형태로 HTTP 응답 맨 뒤에 삽입되어 사용자에게 전송된다. 공격자 파일 중 ROOTROT의 클라이언트로 추정되는 스크립트 /work/mnt/hgfs/Desktop/ivanti_control/main.py가 확인되었다. 해당 스크립트는 주어진 명령어를 실행하도록 작성된 Perl 스크립트를 base64로 인코딩한 뒤 DSPSALPREF 쿠키 값으로 설정하여 HTTP GET 요청을 전송한다. 이는 쿠키 값을 디코딩해 Perl로 실행하는 ROOTROT의 클라이언트 동작과 정확히 일치한다.

또한 main.py는 GET 응답의 마지막 주석을 base64로 디코딩해 사용자에게 표시하는데, 이는 웹쉘 실행 결과를 사용자에게 전달하기 위한 ROOTROT 클라이언트의 표준 동작과 일치한다. 이러한 정보를 근거로 UNC5221과 연관성이 높다고 판단하였다.

그러나 UNC5221 관련 악성코드를 공격자가 공유 받아 사용한 가능성도 있다.

  1. 악성코드 및 공격 가이드

    • 네이버 중간자 공격과 관련있는 디렉토리에서 중국어로 공격 환경 구축에 대해 작성된 readme.txt 파일이 확인되었다.

    • work/mnt/hgfs/share_data/backdoor디렉토리 하위에도 중국어로 작성된 readme.txt 파일과 1.ko 图文编译 .doc(1.ko 그림과 텍스트 편집 .doc), 技术说明书 - 22.docx(기술 설명서 - 22.docx) 파일이 존재한다.

    • 그러나 미완성인 Cobalt Strike Beacon, 공격자가 과거부터 사용한 것으로 보이는 syslogk root은 기능 테스트와 디버그를 위한 로직은 존재하지만 사용 방법이 어디에도 작성되어 있지 않다.

  2. "contact" 키워드가 언급된 악성코드

    • CVE-2025-0282 exploit 관련 악성코드의 detect_version에서 This version may exist vul, Please contact us to check. 문자열을 출력하는 것을 알 수 있고, exp1_admin.py에서도 이를 알 수 있다. 이는 공격자가 CVE-2025-0282 exploit 관련 파일을 다른 공격자나, 조직 내 누군가에게 공유받았을 가능성을 암시한다.

최종적으로 공개된 자료를 분석하였을 때, 공격자는 중국어에 익숙하고 중국 소프트웨어, 커뮤니티, 서비스 등을 이용한다. 공격자가 사용하는 OS 또한 중국에서 개발하고, 중국인 외에는 잘 사용하지 않는 것으로 알려진 deepin OS인 것을 근거로 공격자는 중국인으로 추정된다. 또한 UNC5221 공격과 관련있는 악성코드가 다수 확인된 점에서 UNC5221 그룹과 연관있는 것으로 보인다.

6. 마치며

본 보고서는 APT Down - The North Korea Files 보고서와 공개된 자료를 기반으로 상세 분석한 내용을 정리하였다.

특히 공개된 자료는 일반적으로 확보가 어려운 공격자 실제 환경(VMware VM, VPS) 덤프 파일로 공격자의 활동을 구체적으로 들여다볼 수 있어 큰 도움이 되었다.

공개된 자료에는 국내를 표적으로 삼은 공격 흔적과 탈취된 것으로 보이는 국내 기업, 기관과 관련된 자료가 다수 존재한다. 특히 공격자 VM과 호스트에서 확인된 tomcat*_rootkit디렉토리의 루트킷, 백도어 소스코드는 2022년 국내 금융회사 침해사고에서 채증된 악성코드의 소스코드인 것을 확인하였다.

루트킷과 백도어는 2022년 버전 외에 2025년 버전도 존재하는데, 이는 공격자가 지속적으로 업그레이드하면서 장기간 사용해 왔음을 시사한다. 2022년에 루트킷과 백도어를 사용한 실제 공격 사례가 확인되었으나, 해당 악성코드들은 탐지가 어렵고 감염 징후를 쉽게 알 수 없어 적어도 2022년부터 2025년까지 식별되지 않은 추가 공격 사례가 존재할 것으로 보인다. 이에 따라 감염 점검 절차를 "7. syslogk rootkit 감염 여부 점검 방법"에 정리하였다.

공격자는 북한 공격 그룹 Kimsuy가 아닌 UNC5221 그룹과 연관있는 중국인으로 판단하였다. 공격 배후를 확신할 수 있는 근거는 확인되지 않았지만, 다수의 악성코드 관련 파일에서 중국어가 확인되었고 중국 소프트웨어, 커뮤니티, 서비스를 이용하며 한국어나 영어를 중국어로 번역해 확인한 흔적이 존재한다. 그리고 Ivanti Connect Secure 관련 악성코드들은 UNC5221 그룹이 공격에 사용한 악성코드와 일치하는 부분이 다수 확인되었다.

종합적으로 공격자는 과거부터 현재까지 국내를 지속적으로 표적으로 삼아 공격해왔고, 공개 자료에서 루트킷, 1-Day 익스플로잇 악용과 국내 기업·기관 자료도 다수 확인되었다. 이 정황을 종합할 때 공격자는 고도화된 공격 역량과 악성코드 개발 능력을 보유하였고, 실제로 공격에 성공하여 자료 탈취까지 이른 정황이 확인되었다.

7. syslogk rootkit 감염 여부 점검 방법

2022년 침해사고 조사 당시 syslogk 루트킷은 /etc/init.d에 존재하는 스크립트에 의해 부팅될 때 자동으로 로드되었다. 공개된 자료를 통해 syslogk 루트킷을 빌드할 때 설치 스크립트(install.sh)와 제거 스크립트(del.sh) 그리고 로더 스크립트(shservice)가 생성되는 것을 확인할 수 있었다. 이때 생성되는 파일 중 로더 스크립트(shservice)는 2022년 침해사고 조사 당시 확인한 스크립트와 동일하다.

shservice

caption - shservice 파일 내용

따라서 config.sh로 생성된 스크립트와 악성코드를 기반으로 감염 여부를 점검한다.

모든 스크립트는 관리자 계정 또는 권한으로 실행해야 하고 2022, 2025 루트킷 백도어 모두 탐지 가능하다.

7.1. /etc/init.d 은닉 파일 점검

/etc/init.d에 루트킷을 부팅될 때마다 로드하는 스크립트가 존재하는지 확인한다. 스크립트 파일명은 루트킷에서 은닉하는 문자열이어서 일반적으로 탐지가 불가하다.

따라서 /etc/init.d 디렉토리가 차지하는 디스크 용량과 디렉토리 내에서 확인 가능한 모든 파일이 차지하는 디스크 용량의 총 합을 비교하여 숨겨진 파일이 존재하는지 확인하는 스크립트를 제작하였다. 스크립트는 부록 A. check-initd.sh 로 별첨하였다.

7.2. syslogk rootkit 점검

로더 스크립트에 작성된 문자열을 기반으로 syslogk 루트킷 경로를 추출한다. dump를 사용하여 /etc/init.d디렉토리 덤프 파일을 생성하기 dump가 설치되어야 한다.

  • sudo apt install dump

덤프 파일에서 일치하는 문자열을 찾으면 루트킷 경로를 추출하고, 모듈 언로드를 위해 은닉을 해제할 데이터를 찾는다. 데이터를 찾으면 루트킷에 작성하여 은닉을 해제하고 언로드한다.

스크립트는 부록 B. find-syslogk.py, 부록 C. find-syslogk.sh로 별첨하였다.

  • 부록 B. find-syslogk.py는 python 버전 3.7 이상을 사용한다.

dump는 일부 시스템에서 동작하지 않을 수 있어서, 부록 A. check-initd.sh로 은닉된 파일 존재 유무를 확인한 경우 디스크 이미지를 덤프하거나 분석 도구에 마운트하여 점검하기 바란다.

7.3. Backdoor 실행 여부 점검

백도어 프로세스는 /proc/*/cmdline 경로에 대한 글로브 열거 결과를 토대로 직접 접근할 때의 결과를 비교하여 탐지한다. 실행중인 프로세스의 최대 pid를 기준으로 스캔하여 실행중인 프로세스가 많은 시스템에서는 부하가 증가할 수 있다.

스크립트는 부록 D. check-backdoor.py, 부록 E. check-backdoor.sh로 별첨하였다.

  • 부록 D. check-backdoor.py는 python 버전 3.0 이상을 사용한다.

8. 부록

부록 A. check-initd.sh

initd_disk=$(du /etc/init.d/ 2>/dev/null | awk '{print $1}')
initd_files=$(du /etc/init.d/* 2>/dev/null | awk '{print $1}' | awk '{total_size += $1;} END {print total_size;}')

echo "init.d disk usage: ${initd_disk}"
echo "init.d files disk usage sum: ${initd_files}"

if [ "$initd_disk" -ne "$initd_files" ]; then
    echo "/etc/init.d/ might have a hidden module"
fi

부록 B. find-syslogk.py

import subprocess
import os


if os.getuid() != 0:
  print("[-] run as root")
  exit()

def clean():
    if os.path.exists("./dump_object"):
        os.remove("./dump_object")
    if os.path.exists("./modules_list"):
        os.remove("./modules_list")
    if os.path.exists("./dumpfile"):
        os.remove("./dumpfile")
    exit()

def checkpath(filepath):
    filename = os.path.basename(filepath)
    dirlist = os.listdir(os.path.dirname(filepath))
    if filename in dirlist and os.path.exists(filepath):
        return True
    elif os.path.exists(filepath):
        return False
    else:
        print(f"{filepath} not found")
        clean()

subprocess.run(["/bin/sh", "-c", "dump -0f ./dumpfile /etc/init.d/"], text=True)

dump = open("./dumpfile", "rb").read()

shservice = b'#!/bin/bash\n#\ncase "$1" in\n\'start\')\n\t/sbin/insmod '

idx = dump.find(shservice)

suspicious_path = ''
if idx != -1:
    for i in dump[idx + 50:]:
        if i == 10:
            break
        suspicious_path += chr(i)

    print(f"[+] found suspicious module path : {suspicious_path}")
else:
    print("[-] suspicious module path not found")
    clean()

if suspicious_path != '':
    filename = os.path.basename(suspicious_path)
if checkpath(filename) != False:
    print('[-] suspicious module file is not hidden')
    clean()

subprocess.run(["/bin/sh", "-c", f"objdump -s -F -j .rodata.str1.1 {suspicious_path} > dump_object"], text=True)
subprocess.run(["/bin/sh", "-c", f"cp {suspicious_path} ./"], text=True)
subprocess.run(["/bin/sh", "-c", "lsmod | awk '{ print $1 }' > ./modules_list"], text=True)

old_modules_list = open('./modules_list', 'r').read().split('\n')
a = len("Starting at file offset: ")
f = open("dump_object", "r").read()
b = f.find("Starting at file offset: ")
offset = f[b+a:]
c = offset.find(')')
offset = int(offset[2:c], 16)
length = int(f.split('\n')[-2].split(' ')[1], 16) + 16
f = open(f'./{filename}', 'rb').read()
stringlist = f[offset:offset+length].strip(b'\x00').split(b'\x00')
modulenameidx = 1
magicidx = 13
procnameidx = 47
print(f"[+] possible module name: {stringlist[modulenameidx]}")
print(f"[+] possible magic: {stringlist[magicidx]}")
print(f"[+] possible proc name: {stringlist[procnameidx]}")

procpath = ''
procpathlist = []
tmppath = os.path.join('/proc', stringlist[procnameidx].decode())
if os.path.exists(tmppath):
    procpath = tmppath
    print(f"[+] found procpath {procpath}")
else:
    for string in stringlist:
        tmppath = os.path.join('/proc', string.decode())
        if os.path.exists(tmppath):
            procpathlist.append(tmppath)
    if len(procpathlist) > 1:
        print(f"[-] expected one path got {len(procpath)} : {procpath}")
        clean()
    elif len(procpathlist) == 0:
        print("[-] could not find process path")
        clean()
    else:
        procpath = procpathlist[0]

subprocess.run(["/bin/sh", "-c", f"echo -n {stringlist[magicidx].decode()} > {procpath}"], text=True)
subprocess.run(["/bin/sh", "-c", "lsmod | awk '{ print $1 }' > ./modules_list"], text=True)

modules_list = open("./modules_list", "r").read().split("\n")
for modules in modules_list:
    if modules not in old_modules_list:
        print(f"[+] found rootkit module: {modules}")
        subprocess.run(["/bin/sh", "-c", f"/sbin/rmmod {modules}.ko"], text=True)
        print("[+] rmmod complete")
        clean()

for string in stringlist:
    if b'%' not in string and b'/' not in string:
        subprocess.run(["/bin/sh", "-c", f"echo -n {string.decode()} > {procpath}"], text=True)
        subprocess.run(["/bin/sh", "-c", "lsmod | awk '{ print $1 }' > ./modules_list"], text=True)
        modules_list = open("./modules_list", "r").read().split("\n")
        for modules in modules_list:
            if modules not in old_modules_list:
                print(f"[+] found rootkit module: {modules}")
                subprocess.run(["/bin/sh", "-c", f"/sbin/rmmod {modules}.ko"], text=True)
                print("[+] rmmod complete")
                clean()

clean()

부록 C. find-syslogk.sh

#!/bin/bash
set -euo pipefail

split() {
    local -n output_array="${1}"
    local separator="${2}"
    local input_string="${3}"
    
    output_array=()
    
    local temp_string="${input_string}"

    while [[ "${temp_string}" == *"${separator}"* ]]; do
        output_array+=("${temp_string%%"${separator}"*}")
        temp_string="${temp_string#*"${separator}"}"
    done

    output_array+=("$temp_string")
}

find_matches() {
    local -n output_array="${1}"
    local match_string="${2}"
    local -n input_array="${3}"
    
    output_array=()

    for element in "${input_array[@]}"; do
        if [[ "${element}" == *"${match_string}"* ]]; then
            output_array+=("${element}")
        fi
    done
}

main() {
    if [[ ${EUID} -ne 0 ]]; then
        echo "run as root"
        exit 1
    fi

    local output_separator
    printf -v output_separator "\nOUTPUT_SEPARATOR\n"

    # find `shservice`s
    declare -a strings_array
    split strings_array "${output_separator}" "$(dump -0f - /etc/init.d/ | strings -w -s "${output_separator}")"

    local pattern
    printf -v pattern "#!/bin/bash\n#\ncase \"\$1\" in\n'start')\n\t/sbin/insmod"
    declare -a matching_scripts
    find_matches matching_scripts "${pattern}" strings_array
    echo "found ${#matching_scripts[@]} matching scripts"

    # find module paths
    declare -a paths
    paths=()
    for script in "${matching_scripts[@]}"; do
        local regex="insmod[[:space:]]+([^[:space:]]+)"
        if [[ "${script}" =~ ${regex} ]]; then
            local path="${BASH_REMATCH[1]}"
            paths+=("${path}")

        else
            printf "could not parse: %s\n" "${script}"
        fi
    done
    echo "found ${#paths[@]} paths"
    echo "paths = " "${paths[@]}"
    
    # unload modules
    for path in "${paths[@]}"; do
        # find proc dir
        declare -a ko_strings
        split ko_strings "${output_separator}" "$(strings -w -s "${output_separator}" "${path}")"

        declare -a proc_paths=()
        for string in "${ko_strings[@]}"; do
            if [[ -e "/proc/${string}" ]]; then
                proc_paths+=("${string}")
            fi
        done

        if [[ ${#proc_paths[@]} -ne 1 ]]; then
            printf "%s\n" "expected one path, got ${#proc_paths[@]}: " "${proc_paths[@]}" ", skipping"
            continue
        fi
        local proc_path=${proc_paths[0]}

        # save modules list
        mapfile -t a < <(lsmod | awk '{print $1}')

        # unhide module
        for string in "${ko_strings[@]}"; do
            printf "%s" "${string}" > "/proc/${proc_path}"
        done
        echo "unhid module"

        # compare modules list
        mapfile -t b < <(lsmod | awk '{print $1}')
        
        for bb in "${b[@]}"; do
            if [[ -n "${bb}" && ! "${a[*]}" = *"${bb}"* ]]; then
                echo "found rootkit module: $bb"
                /sbin/rmmod "$bb.ko" && echo "rmmod complete"
            fi
        done
    done

}

main "${@}"

부록 D. check-backdoor.py

import os
import glob

if os.getuid() != 0:
  print("run as root")
  exit()

processes = glob.glob("/proc/*/cmdline")
pids = []

def checkpath(checkpath):
    dirlist = os.listdir(os.path.dirname(checkpath))
    if os.path.basename(checkpath) in dirlist and os.path.exists(checkpath):
        return 1
    elif os.path.exists(checkpath):
        return 0
    else:
       return -1

for p in processes:
  pid = p.split("/")[2]

  if not "self" in pid:
    pids.append(int(pid))

max_pid = max(pids) + 10000

for pid in range(1, max_pid):
  fname = "/proc/%u/cmdline"%(pid)

  try:
    open(fname,"rb").close()
  except IOError:
    continue

  if not pid in pids:
    cmdline = open(fname, "rb").read()
    execpath = cmdline.split(b'\x00')[0]
    if b'//' in execpath or (execpath.startswith(b'/') and checkpath(execpath) == 0):
      print("found backdoor process")
      print("pid: {}".format(pid))
      print("backdoor path: {}".format(execpath.decode()))
      print("process cmdline: {}".format(cmdline))

부록 E. check-backdoor.sh

#!/bin/bash
set -euo pipefail

if [[ $EUID -ne 0 ]]; then
  echo "run as root"
  exit 1
fi

checkpath() {
  local path="$1"
  local dir=$(dirname "$path")
  local base=$(basename "$path")

  if [[ -e "$path" ]]; then
    if (cd "$dir" 2>/dev/null && for f in * .*; do [[ "$f" == "$base" ]] && exit 0; done; exit 1); then
      echo 1
    else
      echo 0
    fi
  else
    echo -1
  fi
}

pids=()
for f in /proc/[0-9]*/cmdline; do
  pid=$(echo "$f" | cut -d/ -f3)
  [[ "$pid" == "self" ]] && continue
  pids+=("$pid")
done

max_pid=$(printf "%s\n" "${pids[@]}" | sort -n | tail -n1)
max_pid=$((max_pid + 10000))

for ((pid=1; pid<=max_pid; pid++)); do
  fname="/proc/$pid/cmdline"
  if [[ ! -r "$fname" ]]; then
    continue
  fi

  if ! printf "%s\n" "${pids[@]}" | grep -qx "$pid"; then
    cmdline=$(tr '\000' ' ' < "$fname")
    execpath=$(tr '\000' '\n' < "$fname" | head -n1)

    if [[ "$execpath" == *"//"* ]]; then
      echo "found backdoor process"
      echo "pid: $pid"
      echo "backdoor path: $execpath"
      echo "process cmdline: $cmdline"
    elif [[ "$execpath" == /* ]]; then
      result=$(checkpath "$execpath")
      if [[ "$result" == 0 ]]; then
        echo "found backdoor process"
        echo "pid: $pid"
        echo "backdoor path: $execpath"
        echo "process cmdline: $cmdline"
      fi
    fi
  fi
done

엔키화이트햇

엔키화이트햇

ENKI Whitehat
ENKI Whitehat

오펜시브 시큐리티 전문 기업, 공격자 관점으로 깊이가 다른 보안을 제시합니다.

오펜시브 시큐리티 전문 기업, 공격자 관점으로 깊이가 다른 보안을 제시합니다.

빈틈없는 보안 설계의 시작, NO.1 화이트 해커의 노하우로부터

침해사고 발생 전,
지금 대비하세요

빈틈없는 보안 설계의 시작,
NO.1 화이트 해커의 노하우로부터

침해사고 발생 전,
지금 대비하세요

빈틈없는 보안 설계의 시작,
NO.1 화이트 해커의 노하우로부터

침해사고 발생 전,
지금 대비하세요

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.