woonadz :)

WIN32 API 프로그래밍 - CMD를 이용한 자가 삭제 본문

IT/malware dev

WIN32 API 프로그래밍 - CMD를 이용한 자가 삭제

C_scorch 2023. 11. 3. 21:33
반응형

파일 자가 삭제를 구현하기 위해 인터넷에 있는 여러 정보들을 찾아보았습니다. 하지만 제가 원하던 self-delete 행위를 하는 코드는 없었습니다. 보았던 코드들 중 어떤 코드는 자가 삭제를 위해 파일의 삭제 명령이 담긴 배치 파일을 드랍하여 그 배치 파일을 실행시키는 형식으로 동작하였지만 이건 자가 삭제 행위라고 보기 어렵다고 생각합니다.

그래서 아래 블로그에서 악성코드의 자가 삭제 행위를 분석한 내용을 토대로 C언어로 구현해보았습니다. 처음에는 프로세스가 종료되지 않은 상태에서 강제로 파일에 접근하려고 하니 권한 오류가 발생하여 작업 스케줄러에 파일 삭제 작업을 등록하는 형식으로 구현할까도 생각하였습니다. 이 부분은 다음에 기회가 되면 구현하여 올리도록 하겠습니다.

https://twoicefish-secu.tistory.com/60

 

드로퍼 분석 보고서

1 개요 과제 주제 드로퍼(dropper)의 구조와 원리 [표1 1 프로젝트 주제] 과제 요약 1. 미지의 프로그램에 대한 접근 방법 2. 드로퍼의 표면적인 작동원리 탐색 3. 드로퍼의 세부적인 작동 원리 탐색 [

twoicefish-secu.tistory.com

 

보고서에 따르면 GetModuleFileNameA로 파일의 전체 경로를 얻고 GetShortPathNameA로 해당 파일의 짧은 경로를 얻습니다. 이후에 문자열 복사를 통해 delete 쉘 명령어와 자신의 경로를 합치는 것으로 보입니다. ShellExecute 함수를 통해 이 문자열을 cmd의 인수로 전달하여 파일을 삭제시킵니다.

 

저는 악성코드가 GetModuleFileNameA로 전체 경로를 얻은 후에 굳이 GetShortPathNameA를 한번 더 사용하여 짧은 경로로 변환하는 부분이 이해가 되지 않아 조금 더 찾아보았습니다.

다음과 같은 2가지 이유로 인해 짧은 경로로 변환한 것 같습니다.

  1. 경로의 길이 제한: 실제로 경로에 대한 배열을 지정할 때 MAX_PATH 상수 값만큼 크기를 할당해준 적이 많습니다. 이처럼 너무 큰 길이의 경로가 할당되는 것을 막기 위해 더 짧은 경로로 변환해주는 함수를 사용한 것 같습니다.
  2. 특수 문자 및 공백 : 경로에 특수 문자나 공백이 들어가면 프로그램이 제대로 경로를 처리하기 어려워집니다. 이 같은 문제는 예방하고자 더 짧은 경로로 축소하여 사용한 것 같습니다.

이제 각 API로 넘어가 설명을 하겠습니다.

GetModuleFileNameA : 지정된 모듈을 포함하는 파일의 정규화된 경로를 검색합니다.

DWORD GetModuleFileNameA(
  [in, optional] HMODULE hModule,
  [out]          LPSTR   lpFilename,
  [in]           DWORD   nSize
);

[in, optional] hModule

경로가 요청되는 로드된 모듈에 대한 핸들입니다. 이 매개변수가 NULL 이면 GetModuleFileName은 현재 프로세스의 실행 파일 경로를 검색합니다.

⇒ 현재 프로세스의 실행 파일 경로를 검색하기 위해 NULL 값을 할당하겠습니다.

 

[out] lpFilename

모듈의 정규화된 경로를 받는 버퍼에 대한 포인터입니다. 경로의 길이가 nSize 매개 변수가 지정하는 크기보다 작으면 함수는 성공하고 경로는 null로 끝나는 문자열로 반환됩니다.

경로 길이가 nSize 매개 변수가 지정하는 크기를 초과하는 경우 함수는 성공하고 문자열은 종료 null 문자를 포함하는 nSize 문자로 잘립니다.

⇒ 경로를 받을 버퍼에 MAX_PATH 만큼 크기를 할당해주겠습니다.

 

[in] nSize

TCHARs 단위 의 lpFilename 버퍼 크기입니다 .

⇒ 경로의 최대값인 상수 MAX_PATH를 지정해주겠습니다.

 

반환 값

함수가 성공하면 반환 값은 종료 null 문자를 포함하지 않고 버퍼에 복사된 문자열의 문자 길이입니다. 버퍼가 너무 작아서 모듈 이름을 담을 수 없으면 문자열은 종료 null 문자를 포함하여 nSize 문자로 잘리고 함수는 nSize 를 반환하며 마지막 오류는 ERROR_INSUFFICIENT_BUFFER 로 설정됩니다 .

 

GetShortPathNameA : 지정된 경로의 짧은 경로 형식을 검색합니다.

DWORD GetShortPathNameA(
  [in]  LPCSTR lpszLongPath,
  [out] LPSTR  lpszShortPath,
  [in]  DWORD  cchBuffer
);

[in] lpszLongPath

경로 문자열입니다.

⇒ GetModuleFileNameA에서 구한 긴 경로의 배열을 지정해주겠습니다.

 

[out] lpszShortPath

lpszLongPath가 지정하는 경로의 null로 끝나는 짧은 형식을 수신하기 위한 버퍼에 대한 포인터입니다 .

이 매개변수에 NULL을 전달 하고 cchBuffer 에 0을 전달하면 지정된 lpszLongPath 에 필요한 버퍼 크기가 항상 반환됩니다 .

⇒ 경로를 받을 버퍼에 MAX_PATH 만큼 크기를 할당해주겠습니다.

 

[in] cchBuffer

lpszShortPath 가 가리키는 버퍼의 크기 (TCHARs) 입니다 .

lpszShortPath가 NULL 로 설정된 경우 이 매개변수를 0으로 설정 하십시오 .

⇒ 경로의 최대값인 상수 MAX_PATH를 지정해주겠습니다.

 

반환 값

함수가 성공하면 반환 값은 종료 null 문자를 포함하지 않고 lpszShortPath 에 복사된 문자열의 TCHARs 길이입니다 .

lpszShortPath 버퍼가 너무 작아 경로를 포함할 수 없는 경우 반환 값은 경로와 종료 null 문자를 보유하는 데 필요한 TCHARs 의 버퍼 크기입니다 .

다른 이유로 함수가 실패하면 반환 값은 0입니다. 확장된 오류 정보를 얻으려면 GetLastError 를 호출하세요 .

 

ShellExecuteA : 지정된 파일에 대한 작업을 수행합니다.

HINSTANCE ShellExecuteA(
  [in, optional] HWND   hwnd,
  [in, optional] LPCSTR lpOperation,
  [in]           LPCSTR lpFile,
  [in, optional] LPCSTR lpParameters,
  [in, optional] LPCSTR lpDirectory,
  [in]           INT    nShowCmd
);

[in, optional] hwnd

UI 또는 오류 메시지를 표시하는 데 사용되는 부모 창에 대한 핸들입니다. 작업이 창과 연결되지 않은 경우 이 값은 NULL 일 수 있습니다.

⇒ optional 값이기에 NULL로 지정해주겠습니다.

 

[in, optional] lpOperation

이 경우 수행할 작업을 지정하는 동사라고 하는 null로 끝나는 문자열에 대한 포인터입니다. 사용 가능한 동사 집합은 특정 파일 또는 폴더에 따라 달라집니다. 일반적으로 개체의 바로 가기 메뉴에서 사용할 수 있는 작업은 동사를 사용할 수 있습니다. 일반적으로 사용되는 동사는 다음과 같습니다.

⇒ “open”을 이용하여 cmd 창을 열겠습니다.

 

[in] lpFile

지정된 동사를 실행할 파일 또는 개체를 지정하는 null로 끝나는 문자열에 대한 포인터입니다. Shell 네임스페이스 개체를 지정하려면 정규화된 구문 분석 이름을 전달합니다. 모든 동사가 모든 개체에서 지원되는 것은 아닙니다.

⇒ cmd 창을 여는 것이 목표이기 때문에 “cmd.exe” 값을 입력해주겠습니다.

 

[in, optional] lpParameters

lpFile이 실행 파일을 지정하는 경우 이 매개 변수는 애플리케이션에 전달할 매개 변수를 지정하는 null로 끝나는 문자열에 대한 포인터입니다. 이 문자열의 형식은 호출할 동사에 의해 결정됩니다. lpFile이 문서 파일을 지정하는 경우 lpParameters는NULL이어야 합니다.

⇒ “cmd.exe” 에 전달한 매개변수를 담은 배열을 지정해주겠습니다.

 

[in, optional] lpDirectory

작업의 기본(작업) 디렉터리를 지정하는 null로 끝나는 문자열에 대한 포인터입니다. 이 값이 NULL이면 현재 작업 디렉터리가 사용됩니다. lpFile에서 상대 경로가 제공되는 경우 lpDirectory에 대한 상대 경로를 사용하지 마세요.

⇒ 현재 프로세스에서 작업할 것이기에 NULL 값을 할당하겠습니다.

 

[in] nShowCmd

애플리케이션을 열 때 표시할 방법을 지정하는 플래그입니다. lpFile에서 문서 파일을 지정하는 경우 플래그는 연결된 애플리케이션에 전달됩니다. 처리 방법을 결정하는 것은 애플리케이션에 달려 있습니다. ShowWindow 함수에 대한 nCmdShow 매개 변수에 지정할 수 있는 값이라면 무엇이든 가능합니다.

⇒ 저는 삭제되는 것을 눈으로 확인하기 위해 SW_SHOW 값을 할당하였지만 창을 숨기고 싶다면 SW_HIDE와 같은 값을 할당하시면 됩니다.

 

반환 값

함수가 성공하면 32보다 큰 값을 반환합니다. 함수가 실패하면 오류의 원인을 나타내는 오류 값이 반환됩니다. 반환 값은 16비트 Windows 애플리케이션과의 이전 버전과의 호환성을 위해 HINSTANCE로 캐스팅됩니다.

 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <windows.h>
#include <WinBase.h>

int main(void) {
    char longPath[MAX_PATH];
    char shortPath[MAX_PATH];
    char strCommand[MAX_PATH+8];

    // 현재 실행 중인 프로그램의 전체 경로 얻기
    DWORD length = GetModuleFileNameA(NULL, longPath, MAX_PATH);

    // 긴 파일 경로를 짧은 파일 경로로 변환
    DWORD shortPathLength = GetShortPathNameA(longPath, shortPath, MAX_PATH);

    strcpy(strCommand, "/C del \\"");
    strcat(strCommand, shortPath);
    strcat(strCommand, "\\"");
    printf("%s\\n", strCommand);

    HINSTANCE result = ShellExecuteA(NULL, "open", "cmd.exe", strCommand, NULL, SW_SHOW); // 파일 삭제 명령어 실행
    printf("ShellExecuteA result: %p\\n", result);

    if ((int)result > 32) {
        printf("파일 삭제 성공");
    }
    else if ((int)result <= 32) {
        printf("파일 삭제 실패");
    }

}

 

반응형