int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { return main_spawner(); } unsigned int __cdecl main_spawner() { unsigned int result; // eax@2 DWORD v1; // [sp+8h] [bp-62B8h]@1 struct _STARTUPINFOW StartupInfo; // [sp+Ch] [bp-62B4h]@1 DWORD ExitCode; // [sp+50h] [bp-6270h]@6 WCHAR Filename; // [sp+54h] [bp-626Ch]@3 WCHAR CommandLine; // [sp+6054h] [bp-26Ch]@6 unsigned int v6; // [sp+62ACh] [bp-14h]@1 struct _PROCESS_INFORMATION ProcessInformation; // [sp+62B0h] [bp-10h]@1 v1 = 29 * mem_loc1 + 7 * pow(mem_loc1, 2); v6 = pow(v1 ^ mem_loc2, v1 % 2 + 5); ProcessInformation.hProcess = 0; ProcessInformation.hThread = 0; ProcessInformation.dwProcessId = 0; ProcessInformation.dwThreadId = 0; memset(&StartupInfo.lpReserved, 0, 0x40u); StartupInfo.cb = 68; if ( mem_loc1 <= 0xD0000000 ) { mem_loc1 = v1; mem_loc2 = v6; GetModuleFileNameW(0, &Filename, 0x12Cu); do { if ( process_index > 400 ) return 0; ++process_index; wsprintfW(&CommandLine, L"\"%s\" %u %u %u", &Filename, v1, v6, process_index); CreateProcessW(0, &CommandLine, 0, 0, 0, 0, 0, 0, &StartupInfo, &ProcessInformation); WaitForSingleObject(ProcessInformation.hProcess, 0xFFFFFFFFu); GetExitCodeProcess(ProcessInformation.hProcess, &ExitCode); v1 = ExitCode; v6 = pow(ExitCode ^ v6, ExitCode % 0x1E); } while ( ExitCode ); result = 0; } else { result = 13 * v1 / 0x1B ^ 0x1F2A990D; } return result; } So it seems that the first process spawns 400 extra processes each with 3 arguments: 2 parameters and the process index. But nothing interesting seems to happen anywhere. Let's check the xrefs to process_index. The only procedure linking to it is sub_401160 but it can't be decompiled because of a small trick: .text:00401169 add esp, 80h .text:0040116F sub esp, 80h We change it to NOPs and then try again. BOOL __cdecl sub_401160() { const WCHAR *v0; // eax@1 unsigned int Buffer; // [sp+Ch] [bp-18h]@2 LPVOID lpBaseAddress; // [sp+10h] [bp-14h]@4 SIZE_T NumberOfBytesWritten; // [sp+14h] [bp-10h]@4 LPWSTR *v5; // [sp+18h] [bp-Ch]@1 int pNumArgs; // [sp+1Ch] [bp-8h]@1 unsigned int v7; // [sp+20h] [bp-4h]@2 v0 = GetCommandLineW(); v5 = CommandLineToArgvW(v0, &pNumArgs); if ( pNumArgs == 4 ) { Buffer = _wtoi(v5[1]); v7 = _wtoi(v5[2]); process_index = _wtoi(v5[3]); } else { Buffer = 0xA8276BFAu; v7 = 0x92F837EDu; process_index = 1; } memset(sub_401070((const char *)&unk_407030, Buffer, v7), 0, 0x1Cu); Buffer ^= 0xB72AF098u; v7 ^= v7 * Buffer; WriteProcessMemory((HANDLE)0xFFFFFFFF, mem_loc1, &Buffer, 4u, &NumberOfBytesWritten); return WriteProcessMemory((HANDLE)0xFFFFFFFF, mem_loc2, &v7, 4u, &NumberOfBytesWritten); } Notice how the program seems to be calling a procedure using the parameters and then sets that memory to zero using memset. Let's rename and examine it: void *__cdecl mystery_func(const char *a1, int a2, int a3) { unsigned int len; // kr04_4@1 int v4; // edi@1 int i; // ecx@1 int j; // [sp+8h] [bp-8h]@4 char *allocd_buffer; // [sp+Ch] [bp-4h]@1 len = strlen(a1) + 1; allocd_buffer = (char *)malloc(len); memset(allocd_buffer, 0, 4 * (len >> 2)); v4 = (int)&allocd_buffer[4 * (len >> 2)]; for ( i = len & 3; i; --i ) *(_BYTE *)v4++ = 0; for ( j = 0; j < (signed int)(len - 1); j += 2 ) { allocd_buffer[j] = a2 ^ a1[j]; a2 = rot_left_by_5(a2) ^ 0x2F; if ( !a1[j + 1] ) break; allocd_buffer[j + 1] = a3 ^ a1[j + 1]; a3 = (unsigned __int8)a2 ^ rot_left_by_11(a3); } return allocd_buffer; } It seems to be "attempting crypto". Well how could we check all 400 values? We first need to get the parameters for each process. We do this using Process Monitor, logging API calls and filtering only for the command line. The initial exported CSV looks like: "6:02:03.8944287 PM","clone_technique.exe","38704","Process Create","C:\Temp\clone_technique.exe","SUCCESS","PID: 38712, Command line: ""C:\Temp\clone_technique.exe"" 3026539702 3580248161 2" "6:02:04.0120585 PM","clone_technique.exe","38712","Process Create","C:\Temp\clone_technique.exe","SUCCESS","PID: 38720, Command line: ""C:\Temp\clone_technique.exe"" 466510610 2867152813 3" "6:02:04.1136464 PM","clone_technique.exe","38720","Process Create","C:\Temp\clone_technique.exe","SUCCESS","PID: 38728, Command line: ""C:\Temp\clone_technique.exe"" 2580226910 609694577 4" "6:02:04.2091374 PM","clone_technique.exe","38728","Process Create","C:\Temp\clone_technique.exe","SUCCESS","PID: 38736, Command line: ""C:\Temp\clone_technique.exe"" 3729770 4114200461 5" Filtering leads to this: Command line: ""C:\Temp\clone_technique.exe"" Command line: ""C:\Temp\clone_technique.exe"" 3026539702 3580248161 2 Command line: ""C:\Temp\clone_technique.exe"" 466510610 2867152813 3 Command line: ""C:\Temp\clone_technique.exe"" 2580226910 609694577 4 Command line: ""C:\Temp\clone_technique.exe"" 3729770 4114200461 5 ....................... Command line: ""C:\Temp\clone_technique.exe"" 2941701190 2496288805 397 Command line: ""C:\Temp\clone_technique.exe"" 1288739010 2151119537 398 Command line: ""C:\Temp\clone_technique.exe"" 2134084791 912261120 399 Command line: ""C:\Temp\clone_technique.exe"" 684983738 221932960 400 Command line: ""C:\Temp\clone_technique.exe"" 994078838 1090513760 401 Now we could manually start each process using those commands and investigate the decrypted buffer. But there would be 2 problems: each launched process would spawn 400 other processes and secondly, it would be too tedious and would waste lots of precious time. To solve the first problem we manually patch the spawning and waiting to NOPs. Here's the diff (including the obfuscation mentioned above removed) clone_technique.exe 00001169: 81 90 0000116A: C4 90 0000116B: 80 90 0000116C: 00 90 0000116D: 00 90 0000116E: 00 90 0000116F: 81 90 00001170: EC 90 00001171: 80 90 00001172: 00 90 00001173: 00 90 00001174: 00 90 0000141A: 51 90 0000141B: 6A 90 0000141C: 00 90 0000141D: FF 90 0000141E: 15 90 0000141F: 10 90 00001420: 60 90 00001421: 40 90 00001422: 00 90 00001423: 6A 90 00001424: FF 90 00001425: 8B 90 00001426: 55 90 00001427: F0 90 00001428: 52 90 00001429: FF 90 0000142A: 15 90 0000142B: 0C 90 0000142C: 60 90 0000142D: 40 90 0000142E: 00 90 To solve the second problem we would need to somehow print out the resulting buffer before it's memset to zero. We can use a debugger such as winedbg (given that we will need to script it). Let's break right before the memset is started at 0x401203: .text:004011F5 E8 76 FE FF FF call mystery_func .text:004011FA 83 C4 0C add esp, 0Ch .text:004011FD 8B F8 mov edi, eax .text:004011FF 8B C3 mov eax, ebx .text:00401201 8B CE mov ecx, esi .text:00401203 F3 AB rep stosd $ winedbg clone_patched.exe 781976132 187909120 326 WineDbg starting on pid 0029 0x7b86225f: movl %edi,0x4(%esp) Wine-dbg>b *0x401203 Breakpoint 1 at 0x00401203 Wine-dbg>cont Stopped on breakpoint 1 at 0x00401203 Wine-dbg>x/s $edi KÄ4╩H·╛)uÇ╟╛╒± σΘ¥[aoâ≥┼ß Wine-dbg> We seem to be on to something. Let's script everything and run it: b *0x401203 finish x/s $edi #!/bin/bash IFS=$'\n' for i in `cat all_cmdlines.csv | cut -d' ' -f5-`; do winedbg --file commands clone_patched.exe $i | grep -v Break | grep -v Stop | grep -v movl | grep -v Wine | strings done $ ./script.sh VjGi2 96)fN HisJ&n Vt!eS ant<\ \7[I h#,9 gl;0 E<}'0T jfh `5y^ hY-HK [xB" dIp,`A %O[Z >"P$ b1FA _Ff% 8"lB Y.ZA h'aO no33[ RkNo ntNTf>io Qnl3PW !nz3 >;w* A+q> b`|wD 2c44 Xs[l! w8&lf br4a tZFHg1W nlJ\ Dy} 5w_V _gi6 mg`@ W X~: Oq;1 ncsO YK)'L $DI$ C*Z!< w1AAq8uz ohLm Ze\A XX]9 Qfz6p J#g(q ;C6T ;d9 R,`# '}\y& WtID *H8p) _:yK @8}U %7"Z j3?^ !E+\ q\ - KrNBF c&lD OEmG n|HP @ZM7 L.xk ->hl J!)H And Now His Watch is Ended .XZ{d jCmm" sj?3 ?$/}R &l*n-F Ux73 "ut< Hmzv 13m=u rAO[/ g]&Rb4 :r[, -SeG KnC~ Lz`)` -"BM 7G~\ 1?T$ #@~} DIi7L x0q4x .{l 7u3T :9%W kQ]_ %l^N '!!s c5f$ 4qD02 2]t% WahO +1{F 3F?e hD[*/ There it is, in plain sight: "And Now His Watch is Ended".