#dokydoky

[분석보고서] 악성코드 - DarkSeoul 본문

Malware

[분석보고서] 악성코드 - DarkSeoul

dokydoky 2013. 9. 1. 20:31

 

<악성코드 상세 분석 보고서>

 

 - 분석대상 : DarkSeoul

 - 작성자 : 김영성

 

 

먼저, 패킹여부를 확인하기 위해 PEiD로 확인

 

 

 

Section Name을 보고 UPX로 압축되어 있다는 것을 확인.

 

 

 

UPX Unpacker를 이용해 Unpack

 

 

 

Unpacked 된 후의 PEiD로의 확인한 모습

 

 

 

 

이제부터 Ollydbg를 이용해 정적분석

 

 

WinMain에 진입 후,

 

memset( addr, 0x00, 0x103 ) 으로 0x103크기의 배열을 0으로 초기화 한 후,

 

LoadLibraryA("Kernel32.dll") 호출


 

 이름

 LoadLibrary (LoadLibraryA는 LoadLibrary의 ASCII 버전)

 기능

 명시된 모듈을 호출한 프로세스 주소공간에 로드한다. 명시된 모듈이 다른 모듈들을 로드할 수도 있다.

 원형

HMODULE WINAPI LoadLibrary(
  _In_  LPCTSTR lpFileName
);

 반환값

 성공시 - 모듈의 핸들

 실패시 - NULL

(확장된 Error 정보를 얻기위해서는 GetLastError를 호출한다)

출처 : MSDN

 

여기까지의 스택에 할당된 자료형은 아래와 같다.

전체할당크기:0x574


---------------0x0012F9BC(esp)(size:2)
".."
---------------0x0012F9BE(size:1)
0x00
---------------0x0012F9BF


---------------0x0012FE20(size:1)
0x00
---------------0x0012FE21(size:0x103)
0으로 초기화(배열)
---------------0x0012FF24(size:8)
??
---------------0x0012FF2C(size:4)
EAX^ESP 
---------------0x0012FF30(ebp)

 

 

 

 

GetProcAddress(hKernel32DLL, funcName)을 호출하여

순차적으로 FindResourceA, LoadResource, LockResource, SizeofResource의 주소를

[417C48], [417C44], [417C40], [417C3C] 에 저장한다.

 

 

 이름

 GetProcAddress

 기능

 명시된 DLL로부터 export된 함수나 변수의 주소를 반환한다.

 원형

FARPROC WINAPI GetProcAddress(
  _In_  HMODULE hModule,
  _In_  LPCSTR lpProcName
);

 반환값

 성공시 - 함수나 변수의 주소

 실패시 - NULL

(확장된 Error 정보를 얻기위해서는 GetLastError를 호출한다)

출처 : MSDN

 

 이름

 FindResource

 기능

 명시된 모듈안에서 명시된 타입과 이름의 자원의 위치를 알아낸다.

 원형

HRSRC WINAPI FindResource(
  _In_opt_  HMODULE hModule,
  _In_      LPCTSTR lpName,
  _In_      LPCTSTR lpType
);

 반환값

 성공시 - 명시된 자원의 information block의 Handle

 실패시 - NULL

(확장된 Error 정보를 얻기위해서는 GetLastError를 호출한다)

출처 : MSDN

 

 

함수의 주소를 저장 후, FindResourceA(hInstance, 0x81, "BIN") 를 호출 한다.

이 자원은 PEView를 통해 확인해볼 수 있다.

 

 

 

위와 같이, PEView의 SECTION .rsrc 를 보면 위와 같은 resource가 추가되어 있음을 볼 수 있다.

악성코드는 아마 이 4개의 자원을 드롭할 것이다.

 

 

 

hRsc81 = FindResourceA(hInstance, 0x81, "BIN")

if( !hRsc81 ){

if( !fun_00402200( hRsc81, "alg.exe", hInstance ) ){

hRsc82 = FindResourceA(hInstance, 0x82, "BIN")

 

if( !hRsc82 ){

if( !fun_00402200( hRsc82, "conime.exe", hInstance ) ){

 

.......

 

}

}

}

}

 

위와 같이, 자원의 핸들을 얻고, fun_00402200을 반복적으로 호출하는 것을 볼 수 있다.

아마 매개변수로 넘겨진 이름을 가지고 어딘가에 파일을 생성하는 함수같다.

 

 fun_00402200

 

 


resource = LoadResource(hInstance, hRsc81);

if( resource == NULL ){

//error처리

}

if( LockResource(resource) ){

//error처리

}

 

 이름

 LoadResource

 기능

 메모리안의 지정된 자원의 첫 바이트의 포인터를 얻을 수 있는 핸들을 반환한다.

 원형

 HGLOBAL WINAPI LoadResource(
  _In_opt_  HMODULE hModule,
  _In_      HRSRC hResInfo
);

 반환값

 성공시 - 리소스와 관련된 데이터의 핸들

 실패시 - NULL

(확장된 Error 정보를 얻기위해서는 GetLastError를 호출한다)

출처 : MSDN

 

 이름

 LockResource

 기능

 메모리안에 지정된 자원의 포인터를 반환한다.

 원형

LPVOID WINAPI LockResource(
  _In_  HGLOBAL hResData
);

 반환값

 성공시 - 자원의 첫바이트의 포인터

 실패시 - NULL

출처 : MSDN

 

 

 

[ebp-108]에 0x104크기만큼의 char배열을 초기화 한 후,

GetTempPathA(0x103, &buffer)로 TempPath경로를 저장했다.

 

이후에는 저장할 프로그램의 이름인 alg.exe를 tempPath의 끝에 붙이는 작업을 통해

결국, [ebp-108]의 주소에 아래와 같은 경로를 저장했다.

 

 

 

위에서 완성된 절대경로를 이용해 파일을 생성한다.

fd = fopen(경로, "wb")

if(fd == 0 ){

//error처리

}

 

여기까지 스택의 모습은 다음과 같다.

-----------------0x0012F890

fd

-----------------0x0012F894(size:4)
hInstance
-----------------0x0012F898(size:4)
hRsc81
-----------------0x0012F89C(size:0x104)
TempPath
-----------------0x0012F9A0(size:4)
[41603C]^EBP
-----------------0x0012F9A4(ebp)

 

이후에는 리소스의 첫바이트를 리소스의 모든 바이트와 xor하며 복호화 한다.(2번째, 3번째 바이트의 xor결과 MZ인 걸 봐서 PE파일을 %tempPath%\alg.exe 로 드롭하는 것을 알 수 있다.)

끝으로 위에서 열었던 fd에 write하고 fclose(fd)를 한다.

 

 이름

 SizeofResource

 기능

 명시된 자원의 크기를 바이트로 반환한다.

 원형

DWORD WINAPI SizeofResource(
  _In_opt_  HMODULE hModule,
  _In_      HRSRC hResInfo
);

 반환값

 성공시 - 자원의 크기(byte)

 실패시 - 0

(확장된 Error 정보를 얻기위해서는 GetLastError를 호출한다)

출처 : MSDN

 

 

즉, 이 함수의 기능은 자원을 복호화한 뒤, 임시경로에 파일을 쓰는 것임을 알 수 있다.

앞으로, 함수의 이름을 WriteResourceTempPath라 하겠다.

 

 

위와 같은 방식으로 총 4개를 임시경로에 드롭시켰다.

 

 

 

 

 

AgentBase.exe 까지 임시경로에 복사를 완료하면 00402360 함수를 실행한다.

 

fun_402360 

 

 

 이 함수에서는 0x104크기의 char형 배열을 0으로 초기화한 후, %TempPath%\AgentBase.exe 문자열을 배열에 넣는다.

그리고 "C:\windows\temp\~v3.log"가 존재하지 않으면 AgentBase.exe를 실행한다.

(이는 v3백신이 깔려있지 않으면 AgentBase.exe를 실행하라는 의미)

 

이 함수이름은 ExecAgentBase라 하겠다.

 

 

 이름

 GetVersion

 기능

 현재 운영체제의 버전을 반환한다.

 원형

DWORD WINAPI GetVersion(void);

 반환값

 성공시 - 하위2바이트에 major, minor 버전을 / 운영체제 플랫폼에 대한 정보를 상위 2바이트에 반환

| OS 플랫폼 정보(2byte) |  minor 버전(1byte) | major 버전(1byte) |

                                        0xxxxxxx                     5

 실패시 - 0

(확장된 Error 정보를 얻기위해서는 GetLastError를 호출한다)

출처 : MSDN

 

버전에 따라 "C:\Documents and setting\*.*" 혹은 "C:\users\*.*"의 경로를 배열에 저장한다.

(GetWindowsDirectoryA를 호출한 이유는 OS가 깔린 드라이브를 파악하기 위해서이다.)

 

 

 

여기부턴, 위에서 완성한 "C:\Documents and setting\*.*" 을 가지고 파일검색 루프를 돈다.

FindFirstFileA로 파일을 검색하고 FindNextFileA로 다음 파일이 없을 때까지 루프를 돈다.

 

루프에서 가장 먼저 하는 일은 FindAPI를 사용해서 찾은 것이 Directory인지 확인한다.

([ESP+18]은 pFindFileData로 WIN32_FIND_DATA구조체의 처음이자 첫멤버인 파일속성을 의미한다. 아래에 자세히 설명)

 

그리고 크기가 각각 0x103인 3개의 문자배열을 memset함수를 이용해 0x00으로 초기화한다. 이를 통해 루프안에서 3개의 배열이 사용될 것이라는 것을 추측할 수 있다.

 

 

* 파일 검색을 위해 사용하는 API

 

 이름

 FindFirstFile

 기능

 명시된 이름과 일치하는 폴더, 파일, 하위폴더를 찾는다.

 원형

HANDLE WINAPI FindFirstFile(
  _In_   LPCTSTR lpFileName,
  _Out_  LPWIN32_FIND_DATA lpFindFileData
);

 반환값

 성공시 - 이후에 FindNextFile, FindClose를 호출할 때 사용하는 검색 handle 반환, lpFindFileData인자는 첫째로 발견되는 파일이나 디렉토리의 정보를 포함한다.

 실패시 - INVALID_HANDLE_VALUE(-1) 리턴. lpFindFileData는 규정되지 않음.

출처 : MSDN

 

 이름

 FindNextFile

 기능

 이전 호출(FindFirstFile,FindFirstFileEx, FindFirstFileTransacted)에 이어 검색을 계속한다.

 원형

BOOL WINAPI FindNextFile(
  _In_   HANDLE hFindFile,
  _Out_  LPWIN32_FIND_DATA lpFindFileData
);

 반환값

 성공시 - nonzero, lpFindFileData인자는 다음으로 발견되는 파일이나 디렉토리의 정보를 포함한다.

 실패시 - zero. lpFindFileData는 규정되지 않음.

출처 : MSDN

 

 이름

 FindClose

 기능

 FindFirstFile... 등등 파일에 의해 열린 파일검색 핸들을 닫는다.

 원형

BOOL WINAPI FindClose(
  _Inout_  HANDLE hFindFile
);

 반환값

 성공시 - nonzero.

 실패시 - zero.

출처 : MSDN

 

 

lpFindFileData는 WIN32_FIND_DATA 구조체의 포인터를 반환한다.

 

typedef struct _WIN32_FIND_DATA {
  DWORD    dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD    nFileSizeHigh;
  DWORD    nFileSizeLow;
  DWORD    dwReserved0;
  DWORD    dwReserved1;
  TCHAR    cFileName[MAX_PATH];
  TCHAR    cAlternateFileName[14];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;

이중 첫 멤버인 dwFileAttributes는 찾은 파일혹은 디렉토리의 속성값을 나타낸다.

FILE_ATTRIBUTE_DIRECTORY (0x10) : The handle that identifies a directory.

 

 

3개의 배열을 초기화한 후에는 direcory이름이 "."인지 확인하고 맞다면 FindNextFile 호출문으로 점프해 다음 파일을 찾는다.

 

 

같은 방식으로 이름이 ".."이면 FindNextFile 호출문으로 점프하고

 

 

추가로 이름안에 "All Users", "Default", "Public"이 포함되어 있어도 FindNextFile 호출문으로 점프한다.

 

 

위에서 초기화했던 3개의 배열중 하나에 "C:\Documents and settings\*.*"를 복사한 후, 404BF0함수를 호출하여 문자열을

"C:\Documents and settings\" 로 바꾼다.

 

그리고 현재 찾은 디렉토리이름의 길이를 구하고 EDI가 "C:\Documents and settings\"의 끝을 가리키게 한 후, "C:\Documents and settings\디렉토리이름" 의 문자열을 만들어 낸다.

 

fun_404BF0

 

 

매개변수로 넘어온 문자열 "C:\Documents and setting\*.*"에서 두 번째 매개변수인 '\'의 포인터값을 리턴해주는 함수

즉, strrchr이다.

 

 

 

추가로, GetVersion API를 통해 얻은 버전에 따라 위에서 초기화한 3개의 배열중에 남은 2개의 배열에

 

"c:\Documents and settings\Administrator\Local Settings\Application Data\Felix_Deimel\mRemote\confCons.xml"

"c:\Documents and settings\Administrator\Application Data\VanDyke\Config\Sessions"

 

와 같은 문자열을 만든다.

 

 

 

최종적으로 이렇게 만들어진 문자열을 이용해서

 

"c:\Documents and settings\Administrator\Local Settings\Application Data\Felix_Deimel\mRemote\confCons.xml"

가 존재하면 4033A0함수를 호출하고

 

"c:\Documents and settings\Administrator\Application Data\VanDyke\Config\Sessions"

가 존재하면 404370함수를 호출한다.

 

이 문자열의 의미는 둘 다 콘솔프로그램이고 첫번째는 mRemote라는 프로그램, 두번째는 VanDyke사의 SecureCRT가 설치되어 있는지확인하는 것이다.

 

 이름

 PathFileExists

 기능

 파일이나 폴더와 같은 파일시스템 객체에 대한 경로가 유효한지 확인한다.

 원형

BOOL PathFileExists(
  _In_  LPCTSTR pszPath
);

 반환값

 성공시 - TRUE

 실패시 - FALSE

출처 : MSDN


 

 fun_4033A0

 

 

memset(*s, 0, 270F) 호출 후,

fopen("~~confCons.xml", "r");  mRemote 프로그램의 설정파일을 읽기 전용으로 연다.

feof 함수를 이용해 열린 파일의 포인터가 EOF인지 확인.

 

 

 

 

다시 memset으로 초기화를 한 후,

위에서 open한 confCons.xml 파일에서 초기화한 배열에 내용을 읽어온다.

 

이후에는 strstr함수를 이용해 "<Node" 문자열과 "Username="root"" 라는 문자열이 있는지 확인한다.

 

 

 

추가로 "Protocol="SSH" 라는 문자열이 있는지 확인하고, " Passwod=""" 라는 문자열은 없는지 확인한다.

여기까지 정리해보면 아래와 같다.

 

if( strstr(&s, "<Node") )

{

if( strstr(&s, "Username="root"" ) )

{

if( strstr(&s, "Protocol="SSH") )

{

if( !strstr(&s, " Passwod=""" ) )

{

......

}

}

}

}

 

그리고 [ebp-218], [ebp-637] 의 0x103크기의 배열을 0으로 초기화 한다.

 

 

 

추가로 4개의 0x103크기의 배열을 초기화 한다.(memset)

 

 

이후에 4032E0함수를 인자를 바꾸어 연속 호출하는데 0x4032E0 함수를 따라가 보겠다.

 

 fun_4032E0

 

 

[ebp-108]위치의 0x103크기의 배열을 0으로 초기화한다.(memset)

[ebp-108]배열에 sprintf([ebp-108], " %s="", 매개변수문자열); 의 문자열을 만든다.

 

confCons.xml을 전체 읽어들인 배열에서 [ebp-108]배열에서 만든 문자열의 위치를 찾는다.

 

 

 

 

루프문은 [ebp-108]배열의 문자열의 길이를 알기 위한 목적이다. 문자열의 길이를 알아낸 후,

다시 contCons.xml의 배열부분에서 " %s=""의 위치를 찾는다.

 

그 후엔, 해당 배열의 위치에서 문자열의 길이만큼 더한 후. 두 번째 큰따옴표까지의 포인터를 구한다.

[ebp-108]배열의 처음 ~ 두번째 큰 따옴표 위치까지의 길이를 구한다.(크기가 0x103이 넘으면 종료)

 

 

 

이 함수의 최종적인 목적부분이다.

이 함수의 목적은 매개변수로 넘긴 hostname=" 뒤의 문자열을  매개변수로 넘긴 배열에 저장하는 것이다.

 

이 함수의 명을 GetCotentFromXML 이라 하겠다.


 

이제 다시 전체적인 흐름으로 돌아가서 생각해보면, 연속적으로 GetContentFromXML함수를 호출한 이유는

각각의 배열에 xml파일에 저장되어 있는 Hostname, Descr, Panel, Port, Password를 저장하기 위함이다.

 


 

 

 

 

 

 

 

 

 

 

 

Comments