바이러스 백신 엔진을 만드는 방법

보내기

사용자 평가 4.54(13 투표)

소개

기술 전문가 포럼을 돌아 다닐 때,나는 종종 어떤 사람들(그리고 많은 경험이 많지 않은 사람들)이”어떻게 바이러스 백신을 만들 수 있습니까?”라고 묻는 것을 종종 봅니다.

나는 또한 많은 보았다”안티 바이러스 소프트웨어”꼬마에 의해 만들어진,거의 아직-에서-학교 사람들과 약 4 몇 주에 코딩의 하루 시간. 나는 꼬마가 숙련되지 않은 말하고 있지 않다,하지만 난 안티 바이러스 엔진을 구축 말하는거야(경우에 그들은 자원 봉사하지 않습니다)그들에게 지불 괜찮은 소프트웨어 또는 많은 돈을 출시 할 시간이 풀 타임 직업 플러스 많은 숙련 된 사람들 중 하나를 많이 필요.

그래서,나는 여기에 기본적인 안티 바이러스 코딩에 대한 지침을 다룰 것이다. 하나는 여기에 바이러스 백신 엔진을 설계 할 수있는 포인터를 찾을 수 있습니다,또는 단순히 대부분이 구축 방법을 배울 수 있습니다.

보호

좋은 보호를 위해 바이러스 백신은 커널에서 코드를 실행할 수 있는 드라이버가 하나 이상 있어야 합니다. 비스타를 시작으로,마이크로 소프트는 바이러스 백신 산업은 커널을 입력하고 파일 시스템,레지스트리 및 네트워크와 같은 전략적 장소에서 필터를 활성화하는 키를 필요로 이해했다. 사전 비스타 시스템에 대한 바이러스 백신을 구축하는 것은 진짜 고통이 될 수있는 경우이를 위해 설계되지 않았기 때문에,기절하지 마십시오.

  • 그러나,사전 비스타 시스템에서 바이러스 백신 회사(마이크로소프트에 의해 전혀 권장 되지 않은 경우에)문을 보호 하 고 시스템을 보호할 수 루트킷과 같은 기능을 사용 하는 데 사용. 그들은 우리가”후크”라고 부르는 것을 사용했습니다.
  • 에 Vista+,Microsoft 에서 제공하는 Api 를 삽입한 낮은 수준 드라이버 사이의 유저랜드 호출하고 커널을 일으킬 수 있습니다. 이렇게 하면 바이러스 백신 제품을 커널에 쉽게 등록할 수 있습니다. 더,그런 종류의 등록 기반 시스템은 우리가 층으로 우리의 시스템 보안을 파견 할 수 있습니다,서로 다른 목적을 가진 여러 제품이 동거 할 수있는 곳. 이것은 구현이 완전히 제품에 의존했기 때문에 후크의 경우는 아니 었습니다.

주: 나는 사전 비스타 시스템에 대한 후크로 해결 방법을 다루지 않을 것이다,그것은 인터넷에서 쉽게 찾을 수 있기 때문에,그것은 후크 방법을 설명하기 위해 전체 장을 필요로하기 때문에,어디 후크 등…하지만 당신은 커널 아피스보다 같은 생각을 알고 있어야합니다,당신은 마이크로 소프트가 비스타+시스템에서 제공 한 것을 자신을 구현해야한다는 것을 제외하고.

코딩 드라이버에 대한 자세한 내용은 해당 유용한 링크를 확인할 수 있습니다:
http://msdn.microsoft.com/en-us/library/windows/hardware/gg490655.aspx
http://www.codeproject.com/Articles/9504/Driver-Development-Part-1-Introduction-to-Drivers

후크에 대한 자세한 내용은 해당 기본 예제를 확인할 수 있습니다:
http://www.unknowncheats.me/forum/c-and-c/59147-writing-drivers-perform-kernel-level-ssdt-hooking.html

프로세스

사용자를 보호하는 첫 번째 일은 악성 프로세스의 시작입니다. 이 기본적인 것입니다. 바이러스 백신은 콜백을 등록해야합니다. 이 작업을 수행하면 각 프로세스 생성 및 주 스레드가 실행(및 악의적 인 원인)되기 전에 바이러스 백신 콜백이 알림을 받고 필요한 모든 정보를 수신합니다.

프로세스 이름,파일 개체,피디 등을 수신합니다. 프로세스가 보류 중일 때,드라이버는 악의적 인 아무것도 프로세스의 메모리를 분석하기 위해 자사의 서비스를 알 수 있습니다. 그것은 무언가를 발견하고,운전자는 단순히 창조 상태를 거짓으로 설정하고 반환 할 것입니다.

NTSTATUS PsSetCreateProcessNotifyRoutineEx( _In_ PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, _In_ BOOLEAN Remove);
VOID CreateProcessNotifyEx( _Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo);
typedef struct _PS_CREATE_NOTIFY_INFO { SIZE_T Size; union { ULONG Flags; struct { ULONG FileOpenNameAvailable :1; ULONG Reserved :31; }; }; HANDLE ParentProcessId; CLIENT_ID CreatingThreadId; struct _FILE_OBJECT *FileObject; PCUNICODE_STRING ImageFileName; PCUNICODE_STRING CommandLine; NTSTATUS CreationStatus;} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;

스레드

프로세스와 동일한 아이디어로 스레드는 악의적 인 것이 손상을 일으킬 수있는 방법이 될 수 있습니다. 예를 들어,합법적 인 프로세스에 코드를 삽입하고 프로세스의 컨텍스트 내에서 해당 코드에 대한 원격 스레드를 시작할 수 있습니다(쉽게 따라 할 수 있습니까? 🙂 ). 그렇게하면 합법적 인 프로세스가 악의적 인 일을 할 수 있습니다.

새 스레드를 필터링할 수 있습니다. 스레드가 생성될 때마다 바이러스 백신에 대해 알립니다. 따라서 스레드의 시작 주소 코드를 살펴보고 분석하고 스레드를 중지하거나 다시 시작할 수 있습니다.

NTSTATUS PsSetCreateThreadNotifyRoutine( _In_ PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine);
VOID(*PCREATE_THREAD_NOTIFY_ROUTINE) ( IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create );

이미지

세 번째 동적 위협은 메모리에 로드할 수 있는 이미지에 관한 것입니다. 이 파일에는 다음과 같은 내용이 포함되어 있습니다. 로드 된 이미지에 대한 알림을 받으려면 간단히 등록하십시오. 이 콜백을 사용하면 이미지가 가상 메모리에로드 될 때 알림을받을 수 있습니다. 그런 다음 프로세스를 로드하거나 드라이버를 로드하거나 새 프로세스를 실행하려고 할 때 이를 감지할 수 있습니다.

콜백은 전체 이미지 경로(정적 분석에 유용 함)에 대한 정보를 얻고 내 의견으로는 이미지 기본 주소(메모리 내 분석)가 더 중요합니다. 이미지가 악의적인 경우 바이러스 백신은 메모리 내 이미지를 구문 분석하고 진입점으로 이동하는 것과 같이 실행을 피하기 위해 약간의 트릭을 사용할 수 있습니다.

NTSTATUS PsSetLoadImageNotifyRoutine( _In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);
VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE)( __in_opt PUNICODE_STRING FullImageName, __in HANDLE ProcessId, __in PIMAGE_INFO ImageInfo );
typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8; //code addressing mode ULONG SystemModeImage : 1; //system mode image ULONG ImageMappedToAllPids : 1; //mapped in all processes ULONG Reserved : 22; }; }; PVOID ImageBase; ULONG ImageSelector; ULONG ImageSize; ULONG ImageSectionNumber;} IMAGE_INFO, *PIMAGE_INFO;

파일 시스템

모든 동적 사물이 보호되면 바이러스 백신은 곧 시작될 때뿐만 아니라 악의적 인 사물에 대해 사용자에게 즉시 알릴 수 있어야합니다. 바이러스 백신은 사용자가 폴더,아카이브를 열거 나 디스크에 다운로드 할 때 파일을 검색 할 수 있어야합니다. 더,바이러스 백신은 파일을 삭제하는 모든 프로그램을 금지하여,자신을 보호 할 수 있어야한다.

이 모든 작업을 수행하는 방법은 파일 시스템에 드라이버를 설치하는 것이며,보다 구체적으로는 레거시 필터(이전 방식)의 미니 필터를 설치하는 것입니다. 여기서 우리는 미니 필터에 대해 이야기 할 것입니다.

미니필터는 파일 시스템에서 수행된 모든 읽기/쓰기 작업에 콜백을 등록할 수 있는 특정 종류의 드라이버입니다. 이 객체는 드라이버 스택과 함께 전송되는 디스크에서 읽기/쓰기 작업을 설명하는 데 사용됩니다. 미니 필터는 단순히 그 스택에 삽입 될 것이고,그 스택으로 무엇을 할 것인지 결정할 수 있습니다(허용/거부 작업).

미니 필터의 작은 예를 들어,그 유용한 링크 또는 하나를 확인하시기 바랍니다. 마이크로 소프트 가이드 라인은 여기에 있습니다.

여기와 여기에서도 두 가지 예를 찾을 수 있습니다.

기본 미니 필터 콜백은 다음과 같습니다. 쿼리 후 전에 필터링 할 수있는 콜백,사전 작업 및 사후 작업의 2 가지 종류가 있습니다. 다음은 수술 전 의사 코드입니다:

FLT_PREOP_CALLBACK_STATUS PreOperationCallback (__inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID *CompletionContext){ ... if ( all_good ) { return FLT_PREOP_SUCCESS_NO_CALLBACK; } else { // Access denied Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; return FLT_PREOP_COMPLETE; } }

레지스트리

레지스트리는 보호 할 수있는 가장 중요한 장소 중 하나입니다. 레지스트리에 하나의(또는 몇)키/값을 등록하여 시스템에 지속적으로 손을 유지하는 악성 코드에 대한 많은 여러 가지 방법이 있습니다. 가장 잘 알려진 장소는 실행 키 및 서비스입니다. 이 또한 바이러스 백신이 더 이상 시스템 부팅시 다시 시작되지 않도록,단순히 드라이버/서비스 키를 제거하여,(파일 시스템과 함께)패배 할 수있는 장소입니다.

이것은 바이러스 백신이 재시작 장소를 보호 할 필요는 없으며 대부분 그렇지 않습니다.그러나 악성 코드에 의해 쉽게 패배하지 않도록 설치 레지스트리 키를 보호해야합니다. 이 작업을 수행 할 수 있습니다.

콜백은 전체 키 이름,액세스 종류(만들기,이름 바꾸기,삭제 등)및 호출자를 가져올 수있는 충분한 정보를 제공합니다. 이 방법은 포스트 작업 콜백의 상태 필드를 설정하여 호출에 대한 액세스 권한을 부여하거나 부여하지 쉽습니다.

NTSTATUS CmRegisterCallbackEx(_In_ PEX_CALLBACK_FUNCTION Function,_In_ PCUNICODE_STRING Altitude,_In_ PVOID Driver,_In_opt_ PVOID Context,_Out_ PLARGE_INTEGER Cookie,_Reserved_ PVOID Reserved);
EX_CALLBACK_FUNCTION RegistryCallback;NTSTATUS RegistryCallback( _In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2){ ... }Argument1 = typedef enum _REG_NOTIFY_CLASS { RegNtDeleteKey, RegNtPreDeleteKey = RegNtDeleteKey,...Argument2 = typedef struct _REG_POST_OPERATION_INFORMATION { PVOID Object; NTSTATUS Status; PVOID PreInformation; NTSTATUS ReturnStatus; PVOID CallContext; PVOID ObjectContext; PVOID Reserved;} REG_POST_OPERATION_INFORMATION, *PREG_POST_OPERATION_INFORMATION;

네트워크(방화벽)

사용자 랜드에서 발생하는 컨텍스트 전환으로 인해 속도가 느려지지 않고 특정 시스템(서버,거대한 대역폭 사용자)에 큰 수있는 전체 인터넷 트래픽의 문을 보호하기 위해,완전히 기본 드라이버가없는 방화벽을 설치하지 않는 것이 좋습니다.

방화벽을 올바르게 구현하려면 다음과 같이 코드를 작성해야 합니다. (내 의견으로는 다른 필터보다 더 많은 지식을 필요로 할 것입니다.)

어쨌든,여기에 이러한 드라이버를 코딩을 시작하는 몇 가지 포인터입니다,마이크로 소프트 가이드 라인,이전 코드 프로젝트 튜토리얼(하지만 여전히 읽기 좋은),방화벽의 예,및 티디 방화벽의 예. 그러나 트로이 뱅커에 대한 추가 모듈이 될 수 있으며,보다 구체적으로는 프로세스 스파이에 대한 모듈이 될 수 있습니다. 그들은 일반적으로 여러 가지 이유로 모든 프로세스에 주입됩니다.

첫째,그들은(필요에 따라)이 악성 코드로 확인 된 경우 프로세스를 죽일 수 있습니다(이 일이 안,이 시작 하기 전에 그것을 중지 하기로 되어 있기 때문에). 그것은 당신이 그 문맥에있을 때 프로세스를 중지하는 것이 더 쉽습니다.

두 번째 그들은 암호,은행 정보를 수집 하 고 악성 코드 서버에 인터넷 흐름을 리디렉션하기 위해 호출을 우회 하 고 필터링 할 수 있는 악성 코드를 연결 하지 않도록 웹 브라우저와 같은 중요 한 프로세스를 보호할 수 있습니다. 이 도구는 감염을 교체하고 또한 컴퓨터가 더 빨리 만들 수 있습니다.모든 프로세스에 프로텍터를 삽입하는 쉬운 방법은 프로텍터를 등록하는 레지스트리 키를 사용하는 것입니다. 시스템 익스플로러는 저희의 데이터베이스를 통해 실행 중인 프로세스를 검사할 수 있는 쉬운 방법을 제공하는 최고의 프리웨어입니다.이미지(대부분 수행).

분석 엔진

분석 엔진은 가장 중요한 부분 중 하나이며 드라이버에서 나오는 파일/메모리 샘플 분석을 담당합니다. 빠른 경우(거대한 데이터베이스로도)대부분의 파일 형식(자체 추출 실행 파일,아카이브,압축 파일,압축 파일 등)을 처리 할 수 있어야하므로이 작업을 수행 할 수있는 많은 모듈이 있어야합니다:

  • 서명 엔진:바이러스 백신의 데이터베이스에는 수백만 개의 서명이 포함되어 있으며 엔진은 샘플로 빠르게 검색 할 수 있어야합니다. 따라서 매우 강력한 알고리즘이 그 일부 여야합니다. 몇 가지 예 : 문자열 일치 알고리즘이 있습니다.
  • 샌드 박스:이 모듈은 필요하지 않지만 시스템에 아무런 영향을 미치지 않고 제한된 메모리로 샘플을 실행할 수 있도록 플러스가 될 것입니다. 이는 알려지지 않은 포장업자로 포장 된 샘플의 포장을 푸는 데 도움이 될 수 있으며 휴리스틱 엔진(후 참조)이 의심 스럽거나 악의적 인 것으로 간주 될 수있는 호출을 탐지하는 데 도움이 될 수 있습니다. 여기에 좋은 샌드 박스.
  • 휴리스틱 엔진:위에서 말했듯이,휴리스틱 엔진은 서명을 검색하지 않고 의심스러운 행동(즉. 정적 분석 또는 샌드 박스를 통해 수행 할 수 있습니다.

서명 구문

서명 구문은 서명 엔진이 이해하는 언어의”사전”입니다. 찾을 패턴,검색 방법 및 샘플로 검색 할 위치를 공식화하는 방법입니다. 구문은 연구자가 이해할 수있을만큼 간단하고 모든 사용 사례를 처리 할 수있을만큼 강력하며 더 나은 엔진 성능을 위해 쉽게 구문 분석 할 수 있어야합니다.

바이러스스토탈은 오픈소스인 야라 프로젝트(야라 프로젝트)와 좋은 구문을 개발했다. 그것은 당신 자신의 구문을 만들거나 단순히 그것을 사용할 수있는 좋은 포인터가되어야합니다. 여기에 또한 안티 바이러스에 대한 서명을 만드는 방법에 대한 좋은 게시물 블로그입니다.

서명 예:

rule silent_banker : banker{ meta: description = "This is just an example" thread_level = 3 in_the_wild = true strings: $a = {6A 40 68 00 30 00 00 6A 14 8D 91} $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9} $c = "UVODFRYSIHLNWPEJXQZAKCBGMT" condition: $a or $b or $c}

자체 보호

자체 보호는 바이러스 백신에 매우 중요합니다,악성 코드에 의해 패배되는 것을 방지하고 사용자를 보호하기 위해 계속. 이 때문에 바이러스 백신은 자체 설치를 보호하고 재부팅시 지속성을 유지할 수 있어야합니다.

파일,레지스트리 키,프로세스/스레드,메모리 등 보호 할 곳이 있습니다.

  • 파일 보호는 바이러스 백신의 파일에 대한 특정 규칙(삭제,이름 바꾸기,이동,쓰기에 액세스 할 수 없음)과 함께 미니 필터에 구현됩니다.
  • 레지스트리 보호가 레지스트리 필터로 이루어지며 드라이버 및 서비스의 레지스트리 키에 대한 액세스가 거부됩니다.
  • 드라이버 스레드는 시스템 충돌 없이 커널 모듈을 언로드하는 것이 불가능하기 때문에 보호되어 사용자 랜드 프로세스 인 서비스를 보호 할 수 있습니다.:
  1. 가장 쉬운 방법은 서비스 관리자에서 오류에 대한 규칙을 추가하고 모든 오류 규칙을”서비스 다시 시작”으로 설정하는 것입니다. 이렇게 하면 서비스 관리자가 서비스를 중지하지 않으면 서비스가 다시 시작됩니다. 물론 시스템이 다시 시작(또는 중지)되지 않을 때까지 서비스는 명령을 수락 할 수 없습니다.
491838-428-487-263x300
  1. 보다 일반적인 두 번째 방법은 오버레지스터 콜백을 사용하여 프로세스 핸들에 콜백을 설정하는 것입니다.물론 프로세스가 바이러스 백신이 보호해야하는 경우(예:바이러스 백신의 서비스(예:바이러스 백신의 서비스(예:바이러스 백신의 서비스(예:바이러스 백신의 서비스(예:바이러스 백신의 서비스(예:바이러스 백신의 서비스(예:바이러스 백신의 서비스(예:바이러스 백신의 서비스(예:바이러스 백신의 서비스)),바이러스 백신의 서비스(예:바이러스 백신의 서비스),바이러스 백신의 서비스(예:바이러스 백신의 서비스),바이러스 백신의 서비스(예:바이러스 백신의 서비스)),바이러스 백신의 서비스(예:바이러스 백신의 서비스),바이러스 백신의 서비스(예:바이러스 백신의 서비스)),바이러스 백신의 서비스(예:바이러스 백신의 서비스),바이러스 백신의 서비스(예:).

    물론 프로세스 또는 스레드에 대한 핸들을 잡아야하는 종료 방법을 피하기 위해 중복 핸들과 스레드 유형을 보호해야합니다. 그 콜백의 사용에 대한 작은 예가 있습니다.

    NTSTATUS ObRegisterCallbacks( _In_ POB_CALLBACK_REGISTRATION CallBackRegistration, _Out_ PVOID *RegistrationHandle);
    typedef struct _OB_CALLBACK_REGISTRATION { USHORT Version; USHORT OperationRegistrationCount; UNICODE_STRING Altitude; PVOID RegistrationContext; OB_OPERATION_REGISTRATION *OperationRegistration;} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
    typedef struct _OB_OPERATION_REGISTRATION { POBJECT_TYPE *ObjectType; OB_OPERATION Operations; POB_PRE_OPERATION_CALLBACK PreOperation; POB_POST_OPERATION_CALLBACK PostOperation;} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
    VOID ObjectPostCallback( _In_ PVOID RegistrationContext, _In_ POB_POST_OPERATION_INFORMATION OperationInformation);
    typedef struct _OB_POST_OPERATION_INFORMATION { OB_OPERATION Operation; union { ULONG Flags; struct { ULONG KernelHandle :1; ULONG Reserved :31; }; }; PVOID Object; POBJECT_TYPE ObjectType; PVOID CallContext; NTSTATUS ReturnStatus; POB_POST_OPERATION_PARAMETERS Parameters;} OB_POST_OPERATION_INFORMATION, *POB_POST_OPERATION_INFORMATION;
    typedef union _OB_POST_OPERATION_PARAMETERS { OB_POST_CREATE_HANDLE_INFORMATION CreateHandleInformation; OB_POST_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation;} OB_POST_OPERATION_PARAMETERS, *POB_POST_OPERATION_PARAMETERS;
    typedef struct _OB_POST_CREATE_HANDLE_INFORMATION { ACCESS_MASK GrantedAccess;} OB_POST_CREATE_HANDLE_INFORMATION, *POB_POST_CREATE_HANDLE_INFORMATION;

    이것은 빙산의 눈에 보이는 부분이다. 너가 너의 제품을 매출하고 싶으면 나의 의견안에,1 개(어쩌면)가장 중요한 부분. 사용자는 아름답고 사용하기 쉽고 직관적 인 것을 좋아합니다. 비록 그것이 100%효율적이지 않더라도. 김은 섹시해야 한다.

    GUI 만 빈 껍데,그것만 그래픽 처리,전송/수신 명령 중핵(서비스). 또한 진행률 표시 줄,분석중인 내용,구성 제공 등을 표시합니다. 섹시하지? 🙂

    avast

    아키텍처

    글로벌 아키텍처는 다음과 같은 것일 수 있습니다:

    1. 웹 브라우저 보호,중간
    2. 서비스:관리자 권한입니다. 커널 코드 및 일부 데이터베이스와 함께 의사 결정을 게이트웨이 역할,강력한
    3. 드라이버(들):커널 필터,강력한

    또한 제품 상태를 표시합니다. 아무것도 더,이 그 목적이 아니다. 이 응용 프로그램은 당신에게 아름다운 욕실 꾸미기의 갤러리를 보여줍니다이 경우 모든 프로세스에 대규모로 주입되며,아이티 후크 및/또는 악성 스레드를 찾을 수 있어야 합니다. 그들은 언로드 또는 패배 매우 열심히해야한다. 그들은 중요하지만 중요하지 않습니다.

    서비스가 제품의 핵심입니다. 그것은 죽일 수 있어야한다,또는 적어도 죽이기에 자기 재시작 할 수 있어야한다. 이 서비스는 제품의 모든 모듈 간의 통신을 담당하며 드라이버에 명령을 보내고 사용자로부터 명령을 받고 샘플 분석을 위해 데이터베이스를 쿼리합니다. 이것은 뇌입니다.

    커널 드라이버도 중요합니다. 그들은 시스템에서 일어나는 모든 일에 대한 정보를 수집 촉수,및 의사 결정에 대한 서비스에 전송. 또한 서비스의 결정에 따라 보호 된 장소에 대한 액세스를 거부 할 수 있습니다.

    결론

    강력하고 신뢰할 수 있고 안정적인 안티 바이러스 엔진을 만드는 것은 복잡한 작업입니다. 테스트,테스트 및 많은 테스트가 필요합니다. 당신의 엔진이 수행하고 작업 할 때,당신은 단지 악성 코드를 분석하고 데이터베이스에 서명을 추가하는 연구원을 고용해야합니다. 어서! 2018 년 11 월 1 일(토)~11 월 1 일(일)~11 월 1 일(일)

    • http://www.symantec.com/connect/articles/building-anti-virus-engine

Leave a Reply

이메일 주소는 공개되지 않습니다.