unsigned int __fastcall xor_sub_10012F6D(int encrstr, int a2)
{
unsigned int result; // eax@2 int v3; // ecx@4if ( encrstr ) {result = *(_DWORD *)encrstr ^ 0x86F186F1; *(_DWORD *)a2 = result;if ( (_WORD)result ) {v3 = encrstr - a2;do
{ if ( !*(_WORD *)(a2 + 2) )break;
a2 += 4; result = *(_DWORD *)(v3 + a2) ^ 0x86F186F1; *(_DWORD *)a2 = result;} while ( (_WORD)result );} }
else
{ result = 0; *(_WORD *)a2 = 0;}
return result; }
A closer look at the above C code reveals that the string decryptor routine actually has two parameters: “encrstr” and “a2”. First, the decryptor function checks if the input buffer (the pointer of the encrypted string) points to a valid memory area (i.e., it does not contain NULL value). After that, the first 4 bytes of the encrypted string buffer is XORed with the key “0x86F186F1” and the result of the XOR operation is stored in variable “result”. The first DWORD (first 4 bytes) of the output buffer a2 is then populated by this resulting value (*(_DWORD *)a2 = result;). Therefore, the first 4 bytes of the output buffer will contain the first 4 bytes of the cleartext string.
If the first two bytes (first WORD) of the current value stored in variable “result” contain ‘\0’ characters, the original cleartext string was an empty string and the resulting output buffer will be populated by a zero value, stored on 2 bytes. If the first half of the actual decrypted block (“result” variable) contains something else, the decryptor routine checks the second half of the block (“if ( !*(_WORD *)(a2 + 2) )”). If this WORD value is NULL, then decryption will be ended and the output buffer will contain only one Unicode character with two closing ’\0’ bytes.
If the first decrypted block doens’t contain zero character (generally this is the case), then the decryption cycle continues with the next 4-byte encrypted block. The pointer of the output buffer is incremeted by 4 bytes to be able to store the next cleartext block (”a2 += 4;”). After that, the following 4-byte block of the ”ciphertext” will be decrypted with the fixed decryption key (“0x86F186F1”). The result is then stored within the next 4 bytes of the output buffer. Now, the output buffer contains 2 blocks of the cleartext string.
The condition of the cycle checks if the decryption reached its end by checking the first half of the current decrypted block. If it did not reached the end, then the cycle continues with the decryption of the next input blocks, as described above. Before the decryption of each 4-byte ”ciphertext” block, the routine also checks the second half of the previous cleartext block to decide whether the decoded string is ended or not.
The original Duqu used a very similar string decryption routine, which we printed in the following figure below. We can see that this routine is an exact copy of the previously discussed routine (variable ”a1” is analogous to ”encrstr” argument). The only difference between the Duqu 2.0 (duqu2) and Duqu string decryptor routines is that the XOR keys differ (in Duqu, the key is”0xB31FB31F”).
We can also see that the decompiled code of Duqu contains the decryptor routine in a more compact manner (within a ”for” loop instead of a ”while”), but the two routines are essentially the same. For example, the two boundary checks in the Duqu 2.0 routine (”if ( !*(_WORD *)(a2 + 2) )” and ”while ( (_WORD)result );”) are analogous to the boundary check at the end of the ”for” loop in the Duqu routine (”if ( !(_WORD)v4 || !*(_WORD *)(result + 2) )”). Similarly, the increment operation within the head of the for loop in the Duqu sample (”result += 4”) is analogous to the increment operation ”a2 += 4;” in the Duqu 2.0 sample.
int __cdecl b31f_decryptor_100020E7(int a1, int a2)
{
_DWORD *v2; // edx@1 int result; // eax@2 unsigned int v4; // edi@6v2 = (_DWORD *)a1;if ( a1 ) {
for ( result = a2; ; result += 4 ) {v4 = *v2 ^ 0xB31FB31F; *(_DWORD *)result = v4;
if ( !(_WORD)v4 || !*(_WORD *)(result + 2) ) break;++v2; }
}
else
{ result = 0; *(_WORD *)a2 = 0;}
return result; }