Home Upgrade Search Memberlist Extras Hacker Tools Award Goals Help Wiki Contact

Encrypting code at Function level

Basic Idea
The basic idea of this trick is to create an encrypted function, where only the current function being executed can be decrypted. This is done by modifying the .text section of the executable to contain the encrypted code. Then, before executing each function, we decrypt it. After executing the function, we re-encrypt it to keep the rest of the program encrypted.

Specific Implementation
To implement this trick, we can use the gcc -finstrument-functions option. This option causes a special function (__cyg_profile_func_enter and __cyg_profile_func_exit) to be called before and after each function call, respectively. By using these functions, we can modify the code to perform the encryption and decryption tasks.


Basically something like this,
Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

// Hook function
void __cyg_profile_func_enter(void *this_fn, void *call_site) {
}

void __cyg_profile_func_exit(void *this_fn, void *call_site) {
}

// Main function
int main() {
}

Code
int getfunc(void* func, int mark) {
    int i = 0;
    
    while (1) {
        unsigned char currentByte = *(unsigned char*)(func + i);
        
        if (currentByte == 0xe8) {
            int relativeOffset = *(int*)(func + i + 1);
            int targetAddress = (int)func + i + relativeOffset + 5;
            
            if (targetAddress == mark) {
                break;
            }
        }
        
        i++;
    }
    
    return i;
}

This allows for an easy calculation of the size of the function. We look for the E8 [offset], or call in x86 asm, opcodes, allowing us to trace and find the absolute address of the resultant location.

Code
int keylookup(void* func) {
    int j = 0;
    while (keys[j] != 0 && keys[j] != (int)func)
        j += 2;
    if (keys[j] == 0)
        keys[j] = (int)func;
    return j;
}

void encfn(void* func, int i, const char* key) {
    DWORD a;
    unsigned char* code = func;
    VirtualProtect(code, i, PAGE_EXECUTE_READWRITE, &a);
    for (int j = 0; j < i; j++)
        code[j] ^= key[j % 4];
    VirtualProtect(code, i, PAGE_EXECUTE_READ, &a);
}

In the code, keys is an array that holds the encrypted keys for the functions. keylookup is a function that searches for a function's key in the keys array. If it does not find a key, it generates one.

encfn is a function that performs XOR encryption and decryption on the function code. It takes a function pointer, an integer that represents the length of the function code, and a string that serves as the key for the encryption.

So, the basic idea is -

The function __cyg_profile_func_enter is called before each function call. In this function, we first decrypt the current function (__cyg_profile_func_enter is actually a pointer to the address of the decrypted function). Then, we add the current function to the callstack and call the function using the __cyg_profile_func_exit function pointer.

The function __cyg_profile_func_exit is called after each function call. In this function, we first call the function using the __cyg_profile_func_enter function pointer. Then, we remove the current function from the callstack and re-encrypt the current function.

Code
int exitMark, enterMark;

int fakeEntryPoint();// defined somewhere

int main() {
    srand(time(NULL));
    memset(keysArray, 0, 100 * sizeof(int));
    memset(callstackArray, 0, 100 * sizeof(void*));
    
    exitMark = ((int)__cyg_profile_func_exit);
    enterMark = ((int)__cyg_profile_func_enter);

    printf("%d", fakeEntryPoint()); // Rename original main to avoid conflict
    return 0;
}

[Image: cAzGPf1.png]

And our address are called successfully.
Good job, I have something like this implemented within my own PIC shellcode crypter.

Upon entry there is decryption and upon exit I have re-encryption with a new KEY, although my shit does not rely on some GCC function.
Also one should consider adding multi-threaded support.
This looks interesting to me but wondering if you can possibly get the address of a function or variable held in memory *from a different program/process*. For example, can ProgramB setup a hook on a function in ProgramA, or would they need to be compiled together? Was mostly curious as if this is possible, I could theoretically listen for events / function use in a few different programs and have my program respond with its own logic. For that use case, it wouldn't be to encrypt and decrypt itself.

For the self decrypting, that's to basically avoid detection on runtime? So for example, you could drop this in, successfully go undetected, then protect yourself from runtime detection by using hooks? If this is true, my main question is what prevents the AV or similar from detecting the decrypted version when it runs before it is re encrypted?
(Dec 21, 2023 07:52 PM)Hackers Wrote:  This looks interesting to me but wondering if you can possibly get the address of a function or variable held in memory *from a different program/process*. For example, can ProgramB setup a hook on a function in ProgramA, or would they need to be compiled together? Was mostly curious as if this is possible, I could theoretically listen for events / function use in a few different programs and have my program respond with its own logic. For that use case, it wouldn't be to encrypt and decrypt itself.

For the self decrypting, that's to basically avoid detection on runtime? So for example, you could drop this in, successfully go undetected, then protect yourself from runtime detection by using hooks? If this is true, my main question is what prevents the AV or similar from detecting the decrypted version when it runs before it is re encrypted?

This is good for 3 things:
1. Emulation engines stalling - as the instructions get decoded on the fly, you can't read the correct OP codes hence it must wait for it to end. You effectively kill the emulator by making it to wait (Brute forcing the key) - AV engines care about how much they impact your computer, hence there is a limit for such an attempt. (X cpu cycles)

2. Memory scans that occur once in a while, when 99% of your code is encrypted and decrypted on the fly, resources stripped / headers removed/ sections wiped.

3. Manual analysis is harder, obviously you can also insert some opaque predicates/junk blocks and other nice stuff.

You should also make sure that you support multi-threading.
(Dec 22, 2023 07:32 AM)osn64698 Wrote:  This is good for 3 things:
1. Emulation engines stalling - as the instructions get decoded on the fly, you can't read the correct OP codes hence it must wait for it to end. You effectively kill the emulator by making it to wait (Brute forcing the key) - AV engines care about how much they impact your computer, hence there is a limit for such an attempt. (X cpu cycles)

2. Memory scans that occur once in a while, when 99% of your code is encrypted and decrypted on the fly, resources stripped / headers removed/ sections wiped.

3. Manual analysis is harder, obviously you can also insert some opaque predicates/junk blocks and other nice stuff.

You should also make sure that you support multi-threading.

I did notice the first issue I noticed this morning was having to support asynchronous functions to prevent blocking and also an issue with my program stealing focus. Thank you for the list here, I am going to have plenty of time on a flight today to think about these concepts and more. I can't say I understand #1 on your list right now as much, but #2 and 3 are familiar to me.
encfn is a function that performs XOR encryption and decryption on the function code. // nice so it does nothing really.
"If i had the abilities to neg fuck you now i would - I really do not care what you think. Go away." ~Armada~
(Dec 23, 2023 03:21 AM)NyQuil Wrote:  encfn is a function that performs XOR encryption and decryption on the function code. // nice so it does nothing really.

You can use encfn for both Encryption/Decryption of XOR, state your question clearly.
is there anything like this for MSVC?
I'm Stanley HF's helper bot. The OP (original poster) of this thread wants everyone to see this page and has elected to bump it to the top of the showthread listing. Since HF doesn't want a ton of LQP (Low Quality Posts) this is the best method of bumping your content. There is a small βyte fee for each bump. Any OP can bump any of their threads on HF from the linked icon on the top of their first post.

If you haven't already read the first post of this thread we encourage you to do so.

Thanks,
Stanley
I am the official HF Help Bot.  You might get an automated PM from me with some helpful information.
Please read our Help Docs.
encrypting a function and decrypting the functions is good but you are still accessing the same address
#c/c++# & #asm#  


The way to patch bad code is to remove the bad code, not to add new bad code that hides the old bad code 
(Dec 26, 2023 01:24 AM)HiddenEyE Wrote:  encrypting a function and decrypting the functions is good but you are still accessing the same address

The idea is that the program is encrypting its own code while it's running, so that any memory dump will only show encrypted code, making it difficult to analyze or reverse engineer the program, this thread is unrelated to AV Evasion + All functions are encrypted using a different key and only the currently running function is decrypted.
__cyg_profile_func_enter() is called, which means the control flow is about to enter a function.
Before entering the function, the code of the function is checked against a stored hash value. If the hash values match, the function is decrypted using the provided keys.
After decryption, the control flow enters the decrypted function, and the function calls are now executed from the decrypted memory location. After the function exits, the code of the function is re-encrypted.
Finally, __cyg_profile_func_exit() is called, indicating that the control flow has left the function.

Even though the code is being executed from the same address (where __cyg_profile_func_exit is called), the actual function code that is being executed is different each time, as it is decrypted using a different set of keys.
So your statement is kind of invalid.
(Dec 26, 2023 01:28 AM)Tentco Wrote:  The idea is that the program is encrypting its own code while it's running, so that any memory dump will only show encrypted code, making it difficult to analyze or reverse engineer the program, this thread is unrelated to AV Evasion.

that is great, will try it out then.
can this evade memory scanners from seeing the opcode ?.
though i never triend such with my mem_scan_engine
#c/c++# & #asm#  


The way to patch bad code is to remove the bad code, not to add new bad code that hides the old bad code 
(Dec 26, 2023 01:35 AM)HiddenEyE Wrote:  that is great, will try it out then.
can this evade memory scanners from seeing the opcode ?.
though i never triend such with my mem_scan_engine

The decrypted function's opcodes will still be present in memory, this could potentially maybe bypass some memory scanner still that's not the main motive of this, you can modify it as you want.
(Dec 26, 2023 01:35 AM)HiddenEyE Wrote:  that is great, will try it out then.
can this evade memory scanners from seeing the opcode ?.
though i never triend such with my mem_scan_engine

You can also use it to fully evade static scanners/scantime. It is also much better to use encryption such as : base64/base58 for better entropy.
Some Improvement Suggestions for anyone who uses it as a base, Add multi-threaded support(suggestion by osn64698).
Fix relative offsets. Better Encryption + long key size.
Indirect syscalls for Page Protection change In the provided code, there is a __cyg_profile_func_exit call at the end of each function that is being called. This call is responsible for
cleaning up the stack and restoring the RWX permissions to their original state. However, the issue arises when the called function enters an
infinite loop, such as while(1).
This is because the infinite loop will keep executing without ever returning control to the caller, and therefore, the __cyg_profile_func_exit
call will never be reached. As a result, the RWX permissions for the stack will remain as RW, which leads to crash(because the compiler does
not expect you to exit the loop).

Sleep obfuscation.


Possibly Related Threads...
Thread / Author Replies Views Last Post
by fateerJul 14, 2025 09:24 PM 3 167 Jul 14, 2025 09:24 PM
Last Post: DramaKing
by User9990Jan 19, 2025 01:33 PM 1 184 Jan 19, 2025 01:33 PM
Last Post: Klonopin
by donwazuJun 1, 2024 07:35 AM 9 331 Jun 1, 2024 07:35 AM
Last Post: secret_code