Binary plaintext string encryption: reduction and anti-reduction

Background

The previous article introduced the principle of xorstr and the code of the minimal verification concept. This article looks at how to restore this technology that has been widely used in various malicious samples and security components. If you haven’t read the previous suggestion, Read it first to understand its implementation before reading this article

xorstr’scurrent situation

As the application of related technologies becomes more and more widespread, various attack samples use this tool, and threat sample analysis becomes more and more time-consuming. This is why a restoration tool is needed to combat the above-mentioned obfuscation technology. The application of two open source restoration tools, flare-floss and AntiXorstr, greatly reduces the difficulty of restoring such binary hidden strings.

Restore PlanAnalysis

Flare-floss is an automated binary string heuristic search tool for virus analysis developed and open sourced by mandiant. Flare-floss provides two restoration logics (stack string & tight string) for this type of encryption method. Based on different assumptions, but they can all be used to handle the above encryption, a brief analysis summary of his implementation principle is given below.

stack string: This reduction method of flare-floss is based on a very broad assumption, that is, this string must be based on the stack, constructed and decrypted on the stack. Therefore, he uses simulated execution to simulate the execution of each function one by one in the assembly code, and performs a full dump of the current stack when encountering a function call instruction. Finally, he uses the string plaintext algorithm to find suspected plaintext strings in the dump. Save the results.

Because its assumptions are too broad, it has almost 100% coverage of current open source xorstr-like projects. However, the overly broad assumptions lead to a large number of false positives. For security analysis, too much junk information interferes with the correct analysis. Judgment, this false positive is specifically marked in its code as “don’t run this on functions with tight loops as this will likely result in FPs”

tight string: In order to solve the problem of a large number of False Postives caused by the overly wide assumption of the above solution, flare-floss updated floss2 last year to add a tight string restoration mode. This mode is based on this assumption: There is a loop block in the function that encrypts the string on the stack. When this block goes out, the stack is in the decrypted state, that is, it targets the decrypt inline function part in the above code. The for loop structure in decrypt is the tight string focus. Loop Block, and he also switches from each function call to dumping the function stack and searching for plaintext strings at the exit of each loop block.

floss-stack string floss-stack string
Coverage Excellent+
Bypass difficulty Excellent+
Error Report rate Poor-
Accuracy Poor-

AntiXorstr: When I wrote this tool last year, I didn’t pay attention to the floss project, so the implementation logic was completely different from it. Different from tightstring’s focus on decrypt features, this tool focuses on class constructors. Based on the assumption: “The encrypted value of the stack encrypted string must be calculated at compile time.” The tool will pre-analyze the stack of the function and stain the stack with immediate numbers. The connected areas stained by non-zero immediate numbers will be marked as highly suspicious areas, and the read and write of such areas will be paid attention to during the subsequent simulation execution. Output results

floss-tight string floss-tight string
Coverage Excellent-
Bypass difficulty Medium +
Error Reporting rate Medium +
Accuracy Poor-
AntiXorstr AntiXorstr
Coverage Medium +
Bypass difficulty Excellent-
False alarm rate Excellent+
Accuracy Excellent+

Tight string bypass

In the process of analyzing the tight string logic of floss, I found that his assumption is not actually necessary for stack strings, but it is indeed a feature that must exist in almost all current open source implementations, that is, the decryption function flow chart appears as a loop. form. Therefore, to bypass the floss-tight string is to implement a string decryption function that does not form a loop. First, let’s take a look at the disassembly flow chart characteristics of the current conventional string decryption logic. The demo is as follows

The loc_1400010A0 here is a loop Block, and the code in the Block is the assembly code of the decrypt function that decrypts the original data in the for loop. Here, we can bypass this restoration method without generating a loop inside the decrypt function. So how to eliminate the loop? Think about it here using the idea of template programming. The recursive expansion of templates in loops can be eliminated at compile time. The final desired effect is as follows

__forceinline char* decrypt()
{<!-- -->
    for (auto index = 0; index < N; index + + )
    {<!-- -->
        encBuffer[index] -= 1;
    }
    return encBuffer;
}

//N = 5
__forceinline char* decrypt_noloop()
{<!-- -->
    encBuffer[0] -= 1;
    encBuffer[1] -= 1;
    encBuffer[2] -= 1;
    encBuffer[3] -= 1;
    encBuffer[4] -= 1;
    return encBuffer;
}

The rest is how to use the C++ template to generate the decrypt_noloop code, use recursive expansion to generate it, and make a specialized termination for N=0 to eliminate the loop. There are also some details that will not be expanded here due to space limitations. The DEMO has been implemented. Open source: xorstr_s.h

test demo

void test()
{<!-- -->
    printf(Enc("Samsung\
"));
    wprintf(Enc(L"Apple\
"));
    printf(Enc("Xiaomi\
"));
    wprintf(Enc(L"Oppo Group\
"));
    printf(Enc("vivo\
"));
    wprintf(Enc(L"Transsion\
"));

    std::cout << Enc("Honor") << std::endl;
    std::wcout << Enc(L"Realme") << std::endl;
    std::cout << Enc("Motorola") << std::endl;
    std::wcout << Enc(L"Huawei") << std::endl;
    std::cout << Enc("Others") << std::endl;
}

Test results: STACK & TIGHT bypassed, DECODED mode restored part of the

────────────────────
  FLOSS STACK STRINGS
 ────────────────────

 ────────────────────
  FLOSS TIGHT STRINGS
 ────────────────────

 ──────────────────────
  FLOSS DECODED STRINGS
 ──────────────────────
Samsung
Apple
Xiaomi
Oppo Group
vivo
Transsion
Honor

More robust bypass strategy

The xorstr_s written above through detailed analysis of the floss restoration strategy does not seem to be very robust. It is too costly to implement a customized bypass solution just for an open source tool. So is there a more robust anti-reduction method? For the time being, there is There are two methods, the core principle of one of which is: “Heap-based plaintext expansion”, which makes Floss’s mode of monitoring heap plaintext completely invalid.

Those who have read the previous article should know that the string ciphertext is stored in the stack, but it does not have to be decrypted on the spot. It is completely possible to read only the stack data and write the decrypted plaintext to the heap, and use the temporary object’s The destructor can complete the release of the heap. The DEMO implementation has been open source: xorstr_h.h. The effect of testing FLOSS is as follows

FLOSS STACK STRINGS
 ────────────────────
o]QOIR[6<
}<L<L<P<Y<6<<<
dU]SQU6<
s<L<L<S<
<{<!-- --><N<S<I<L<6<<<
JUJS6
h<N<]<R<O<O<U<S<R<6<<<
tSRSN
n<Y<]<P<Q<Y<<<
qSHSNSP]<
t<I<]<K<Y<U<<<
sHTYNO<

 ────────────────────
  FLOSS TIGHT STRINGS
 ────────────────────

 ──────────────────────
  FLOSS DECODED STRINGS
 ──────────────────────
o]QOIR[6<

Test results: Floss bypass in all modes