diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f838cf2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "external/openssl"] + path = external/openssl + url = https://github.com/openssl/openssl.git + branch = OpenSSL_1_0_2-stable diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba3bbfa --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2021, ESET spol. s r.o. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f4681a2 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +all: external/openssl/build/lib/libssl.a client.exe module.dll + +external/openssl/build/lib/libssl.a: + sudo apt-get install mingw-w64 + chmod +x external/openssl/Configure + cd external/openssl &&\ + ./Configure mingw64 --cross-compile-prefix=x86_64-w64-mingw32- --prefix=$(shell pwd)/external/openssl/build &&\ + make && sudo make install + +client.exe: client.c + x86_64-w64-mingw32-gcc client.c -static -I external/openssl/build/include/ -o client.exe -Lexternal/openssl/build/lib/ -lcrypto -lssl -lgdi32 -lwsock32 -lws2_32 -lkernel32 + +module.dll: module.c + x86_64-w64-mingw32-gcc module.c -static -shared -o module.dll diff --git a/README.md b/README.md new file mode 100644 index 0000000..633a121 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +WslinkClient +============ + +WslinkClient is a client intended to communicate with +[Wslink](https://www.welivesecurity.com/2021/10/27/wslink-unique-undocumented-malicious-loader-runs-server/), which is a unique loader running as a server and +executing received modules in-memory. It was initially made to experiment with +detection methods. + +The client might be of interest to beginners in malware analysis - it shows how +one can reuse existing functions of analyzed malware and interact with it. + +WslinkClient simply establishes connection with Wslink and sends a module which +is subsequently executed. + +The code reuses a few functions from a non-virtualized unpacked sample, which +is available on VirusTotal. SHA-1 of the sample is +``840BBD3475B189DBB65F2CD4E6C060FE3E071D97``. Note that you must still patch +its public key and load it yourself to test it since we do not want to publish +a ready-to-use loader. + +Compilation +=========== + +The code was compiled with the supplied Makefile running on Ubuntu 20.04 +with Linux 5.4.0. The binaries are included in the +[GitHub releases section](https://github.com/eset/wslink-client/releases). diff --git a/client.c b/client.c new file mode 100644 index 0000000..132000e --- /dev/null +++ b/client.c @@ -0,0 +1,266 @@ +// -*- encoding: utf8 -*- +// +// Copyright (c) 2021 ESET spol. s r.o. +// Author: Vladislav Hrčka +// See LICENSE file for redistribution. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "client.h" + +int main(int argc, char **argv) { + if (argc < 3) { + printf("%s: Missing IP or port\nUsage: %s [IP] [PORT]", argv[0], argv[0]); + return -6; + } + struct wslink_functions wsf; + if (!init_wslink_functions(&wsf)){ + printf("Failed to load Wslink dll."); + return -7; + } + SOCKET s = init_connection(argv[1], atoi(argv[2])); + if(!s) { + printf("Connection could not be established."); + return -1; + } + struct tls_context cnt; + cnt.sock = s; + char* private_key = get_private_key(); + if (private_key == NULL) { + printf("Private key could not be read."); + return -5; + } + if (!handshake(&wsf, &cnt, private_key)) { + printf("Handshake failed."); + return -3; + } + if (!send_module(&wsf, &cnt)) { + printf("The module could not be sent."); + return -4; + } + free(private_key); + clear_socket(s); + return 0; +} + +int init_wslink_functions(struct wslink_functions* wsf) { + long long dllBase = LoadLibrary("wslink.dll"); + if (dllBase == 0) { + return 0; + } + void* bin = (void*) dllBase; + + wsf->symmetric_encrypt_send = (int (*)(struct tls_context*, void *, int))(bin + symmetric_encrypt_send_offset); + wsf->receive_wrapper = (int (*)(SOCKET, char *, int, int))(bin + receive_wrapper_offset); + wsf->symmetric_receive_decrypt = (int (*)(struct tls_context*, void*, int))(bin + symmetric_receive_decrypt_offset); + return 1; +} + +// Code from http://hayageek.com/rsa-encryption-decryption-openssl-c/ +RSA * createRSA(unsigned char * key, int isPublic) +{ + RSA *rsa= NULL; + BIO *keybio ; + keybio = BIO_new_mem_buf(key, -1); + if (keybio==NULL) + { + return 0; + } + if(isPublic) + { + rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL); + } + else + { + rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL); + } + return rsa; +} + +// https://stackoverflow.com/questions/9889492/how-to-do-encryption-using-aes-in-openssl +void handleErrors(void) +{ + unsigned long errCode; + + printf("An error occurred\n"); + while(errCode = ERR_get_error()) + { + char *err = ERR_error_string(errCode, NULL); + printf("%s\n", err); + } + abort(); +} + +int aes_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext) { + EVP_CIPHER_CTX *ctx = NULL; + int len = 0, ciphertext_len = 0; + + /* Create and initialise the context */ + if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors(); + + /* Initialise the encryption operation. */ + if(1 != EVP_EncryptInit(ctx, EVP_aes_256_cbc(), key, iv)) + handleErrors(); + + /* Provide the message to be encrypted, and obtain the encrypted output. + * EVP_EncryptUpdate can be called multiple times if necessary + */ + if(plaintext) + { + if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) + handleErrors(); + + ciphertext_len = len; + } + + if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors(); + ciphertext_len += len; + + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + return ciphertext_len; +} + +char* get_private_key() { + FILE *f = fopen("rsa2048.pem", "rb"); + if (!f) { + return 0; + } + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + char *data = malloc(fsize + 1); + fread(data, 1, fsize, f); + fclose(f); + + return data; +} + +void clear_socket(SOCKET s) { + shutdown(s, SD_BOTH); + closesocket(s); + WSACleanup(); +} + +int handshake(struct wslink_functions* wsf, struct tls_context* cnt, char* private_rsa_key) { + char hello[hello_len]; + char encrypted_hello[modulus_len]; + memset(hello, 0, hello_len); + RSA *rsa = createRSA(private_rsa_key, 0); + // sends hello + int rsa_sig_size = RSA_private_encrypt(hello_len, hello, encrypted_hello, rsa, RSA_PKCS1_PADDING); + if (send(cnt->sock, encrypted_hello, rsa_sig_size, 0) == SOCKET_ERROR) { + return 0; + } + + // receives symmetric key + char encrypted_answer[modulus_len]; + char decrypted_answer[answer_len]; + if (!wsf->receive_wrapper(cnt->sock, encrypted_answer, modulus_len, assymetric_timeout)) { + return 0; + } + rsa_sig_size = RSA_private_decrypt(modulus_len, encrypted_answer, decrypted_answer, rsa, RSA_PKCS1_PADDING); + if (answer_len != rsa_sig_size) { + return 0; + } + struct handshake_answer* ha = (struct handshake_answer*) decrypted_answer; + memcpy(cnt->key, ha->key, key_len); + memcpy(cnt->iv, ha->iv, iv_len); + + // sends symmetric key back for verification + char reencrypted_answer[modulus_len]; + rsa_sig_size = RSA_private_encrypt(answer_len, (void*) decrypted_answer, reencrypted_answer, rsa, RSA_PKCS1_PADDING); + if (send(cnt->sock, reencrypted_answer, rsa_sig_size, 0) == SOCKET_ERROR) { + return 0; + } + return 1; +} + +int get_module(struct wslink_module* wsm) { + FILE *f = fopen("module.dll", "rb"); + if (!f) { + return 0; + } + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + void *data = malloc(fsize); + fread(data, 1, fsize, f); + fclose(f); + + void* enc_data = malloc(fsize + (iv_len - fsize % iv_len)); + int enc_size = aes_encrypt(data, fsize, module_key, null_iv, enc_data); + free(data); + + wsm->data = enc_data; + wsm->len = enc_size; + return 1; +} + +int send_module(struct wslink_functions* wsf, struct tls_context* cnt) { + struct module_id prev_mod; + // receive signature of the previously loaded module + if (!wsf->symmetric_receive_decrypt(cnt, (void*) &prev_mod, sizeof(prev_mod))) { + return 0; + } + // send the signature of the module to be sent + if (!wsf->symmetric_encrypt_send(cnt, module_sig, strlen(module_sig))) { + return 0; + } + struct wslink_module wsm; + // load the module from file + if (!get_module(&wsm)) { + return 0; + } + // send the module + if (!wsf->symmetric_encrypt_send(cnt, (void*) &(wsm.len), sizeof(wsm.len))) { + free(wsm.data); + return 0; + } + if (!wsf->symmetric_encrypt_send(cnt, wsm.data, wsm.len)) { + free(wsm.data); + return 0; + } + free(wsm.data); + // send the encryption key of the module + if (!wsf->symmetric_encrypt_send(cnt, module_key, key_len)) { + return 0; + } + return 1; +} + +SOCKET init_connection(char* ip, int port) { + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) { + return 0; + } + + struct sockaddr_in clientService; + clientService.sin_family = AF_INET; + clientService.sin_addr.s_addr = inet_addr(ip); + clientService.sin_port = htons(port); + + SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == INVALID_SOCKET) { + WSACleanup(); + return 0; + } + if (connect(s, (SOCKADDR *) &clientService, sizeof(clientService)) == SOCKET_ERROR) { + clear_socket(s); + return 0; + } + return s; +} diff --git a/client.h b/client.h new file mode 100644 index 0000000..d1bfa76 --- /dev/null +++ b/client.h @@ -0,0 +1,56 @@ +// -*- encoding: utf8 -*- +// +// Copyright (c) 2021 ESET spol. s r.o. +// Author: Vladislav Hrčka +// See LICENSE file for redistribution. + +#define assymetric_timeout 30000 +#define modulus_len 256 +#define key_len 32 +#define iv_len 16 +#define hello_len 0xf0 +#define answer_len 0xf0 +#define symmetric_encrypt_send_offset 0x22c0 +#define receive_wrapper_offset 0x1d10 +#define symmetric_receive_decrypt_offset 0x2410 +char* module_sig = "0123456789abcdef0123456789abcdef"; +char* module_key = "0123456789abcdef0123456789abcdef"; +char null_iv[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +struct tls_context { + SOCKET sock; + char key[key_len]; + char iv[iv_len]; +}; + +struct module_id { + int constant_id; + char module_signature[0x20]; +}; + +struct handshake_answer { + char key[key_len]; + char iv[iv_len]; + char random_padding[0xC0]; +}; + +struct wslink_functions { + int (*symmetric_encrypt_send) (struct tls_context* cnt, void *buff, int buff_len); + int (*receive_wrapper) (SOCKET s, char *buf, int len, int timeout); + int (*symmetric_receive_decrypt) (struct tls_context* cnt, void *buff, int buff_len); +}; + +struct wslink_module { + char* data; + int len; +}; + +int init_wslink_functions(struct wslink_functions* wsf); +SOCKET init_connection(char* ip, int port); +int handshake(struct wslink_functions*, struct tls_context* cnt, char* private_key); +int send_module(struct wslink_functions* wsf, struct tls_context* cnt); +RSA * createRSA(unsigned char * key, int isPublic); +int aes_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext); +char* get_private_key(); +void clear_socket(SOCKET s); +int get_plugin(struct wslink_module* wsm); diff --git a/external/openssl b/external/openssl new file mode 160000 index 0000000..12ad22d --- /dev/null +++ b/external/openssl @@ -0,0 +1 @@ +Subproject commit 12ad22dd16ffe47f8cde3cddb84a160e8cdb3e30 diff --git a/module.c b/module.c new file mode 100644 index 0000000..f3079f5 --- /dev/null +++ b/module.c @@ -0,0 +1,69 @@ +// -*- encoding: utf8 -*- +// +// Copyright (c) 2021 ESET spol. s r.o. +// Author: Vladislav Hrčka +// See LICENSE file for redistribution. + +#include +#include +#include + +// https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpReserved ) // reserved +{ + // Perform actions based on the reason for calling. + switch( fdwReason ) + { + case DLL_PROCESS_ATTACH: + // Initialize once for each new process. + // Return FALSE to fail DLL load. + break; + + case DLL_THREAD_ATTACH: + // Do thread-specific initialization. + break; + + case DLL_THREAD_DETACH: + // Do thread-specific cleanup. + break; + + case DLL_PROCESS_DETACH: + // Perform any necessary cleanup. + break; + } + return TRUE; // Successful DLL_PROCESS_ATTACH. +} + +// https://docs.microsoft.com/en-us/windows/win32/procthread/creating-processes +__declspec(dllexport) void exp1() { + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + // Start the child process. + if( !CreateProcess( NULL, // No module name (use command line) + "C:\\Windows\\system32\\calc.exe", // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi ) // Pointer to PROCESS_INFORMATION structure + ) + { + printf( "CreateProcess failed (%d).\n", GetLastError() ); + return; + } + + // Close process and thread handles. + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); +}