Using components of Windows multilingual support, it is possible to create a file that will capture keystrokes on a target system while using the OS to protect that file from removal or deletion. Masaki Suenaga explains how an IME could be used as a keylogger.
Copyright © 2005 Virus Bulletin
The aim of this article is to outline two potential methods for using an IME as a keylogger: by hacking a genuine IME for the Far East versions of Windows and by creating a fake IME for other versions of Windows. Using components of Windows multilingual support, it is possible to create a file that will capture keystrokes on a target system while using the OS to protect that file from removal or deletion.
To begin, I'll explain what an IME, or input method editor is. The Chinese, Japanese and Korean writing systems use thousands of characters: Hanzi (Chinese characters) in Chinese; Kanji (Chinese characters), Hiragana and Katakana in Japanese; Hangeul and Hanja (Chinese characters) in Korean. To represent these characters, each of these languages has its own multi-byte character code sets. On ASCII code-based Windows such as Windows 95, the double byte character set or DBCS is used, where each two-byte sequence represents one character. While DBCS is no longer commonly used, it is still used on Windows XP if a program does not call Unicode APIs. Starting with Windows 2000, Microsoft's desktop operating systems have primarily used Unicode for cross-compatibility and ease of use.
If a keyboard had thousands of keys, as was once the case with mechanical typewriters, there would be no need to convert multiple keystrokes to a single character. However, most modern keyboards have only around 100 keys. Therefore, we need something to convert keystrokes to characters before being used in an application. This kind of software is called a front-end processor or FEP, and IME is the standard name for FEPs used in Windows environments.
The image above shows some common IME options when the keyboard icon is clicked. The pop-up list shows all the available IMEs or keyboard layouts for a given language.
The following pictures illustrate how a user inputs Chinese characters in Notepad. The IME status bar is shown in the bottom right-hand corner of the Notepad window here, but it can be placed anywhere, and generally is shown either in the bottom right-hand corner of the screen or as part of the Taskbar. In the first screenshot the user has typed 'ni' while IME is ON. The string 'ni' here is called the 'composition string' in the interface.
Next, the user has typed 'nichi'. You can see that 'ni' has disappeared from the little grey box, and the character which is most likely to represent 'ni' is underlined with dashes. Both of these strings are called composition strings because they may not match the final text used in the application.
The third image shows the screen when the user has finished typing the phrase 'nichifanleme' and has pressed the Enter key. There is no little grey box, but the five Chinese characters are underlined with dashes. These characters, 10 bytes in DBCS and five words in Unicode, have not been passed to the application yet. At this stage it is still considered a composition string.
Finally, the user has pressed Enter a second time to confirm the characters in the previous string. This string is called the 'result string', since the user has chosen not to alter any of the IME-selected characters for the keystrokes they typed. Each character is sent to the application window with the WM_IME_CHAR window message. If the window procedure does not process this message, it will receive a WM_CHAR message. So most applications don't need to be aware of IME. (Some applications, such as Microsoft Word and Excel are fully aware of IME and display composition strings by themselves.) These complicated tasks are performed by the IME and IMM (input method manager), and an IMM is included in every language version of Windows with multilingual support (2000 and later).
The process diagram below illustrates how the text input process occurs.
When IMM receives a key stroke from the keyboard driver, it sends the key stroke to the IME through the ImeProcessKey entry to ask the IME if the key stroke should be processed by the IME. If the IME returns zero, the key stroke will be processed by the OS and passed to the application as WM_KEYDOWN and WM_KEYUP, then further processed to WM_CHAR or WM_COMMAND, and so on. The IME will not receive the key through ImeToAsciiEx.
If the IME returns a non-zero value in ImeProcessKey, the IMM sends the character to the IME again.
The IME receives the lpdwTransBuf parameter, which will be set by the IME when the process returns from the IME to the IMM. The lpdwTransBuf parameter contains information about window messages to be sent to the application. The IME also receives the hIMC parameter, which contains composition strings, such as the composition string itself, the result string, and any reading information or clause information, depending on the language. The IME modifies the content of hIMC as it processes characters.
Any time the IMM receives lpdwTransBuf back from the IME, the IMM checks the buffer to see if it contains a message list. Typically it contains the WM_IME_COMPOSITION message, which should be sent whenever the composition string changes. The IMM sends these messages in the buffer to the application window.
If the application is not IME-aware, it will not process the WM_IME_COMPOSITION message and thus the user will not see the text within the application. In this case, the message is relayed to the corresponding IME UI window, which is always created if an IME is activated. An IME UI window will show the composition string as it is typed.
If the application is IME-aware, it will process the WM_IME_COMPOSITION message. If there is a need to get the contents of composition strings, it calls the ImmGetCompositionString API in IMM32.DLL. The WM_IME_COMPOSITION message can also notify that the string is determined and the result string has been generated. If the application gets the determined string directly from IMM and pastes the string into its document, it should not call DefWindowProc on the WM_IME_CHAR message, because further processing will generate the same character twice.
If the application is not IME-aware, it will receive the WM_IME_CHAR message. If the application uses the GetMessageW API (along with DispatchMessageW), it will get one Unicode character in a WM_IME_CHAR. If the GetMessageA API (along with DispatchMessageA) is used, the application receives one DBCS character in the message. If the application does not call DefWindowProc on WM_IME_CHAR, it will not receive the WM_CHAR message later.
If the application is not designed to use IME at all, it will get a WM_CHAR message as the result string is generated. If GetMessageW/DispatchMessageW are used, it receives a Unicode character, which is exactly the same as when getting WM_IME_CHAR. If GetMessageA/ DispatchMessageA are used, it receives two WM_CHAR messages for each DBCS character; the higher byte on the first message, the lower on the second
An IME is a DLL file, typically with the file extension '.IME', and is usually placed in the Windows system directory. IMEs are registered as keyboard layouts in the registry at the following location: HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Keyboard Layouts (see below). The IME has an 'IME file' value ('winpy.ime'). When these registry values are set, a user can choose to enable this IME through the Control Panel. Once it has been enabled, the IME will be shown in the list of keyboard layouts that we saw earlier.
The registry key HKEY_CURRENT_USER\KeyboardLayout\Preload contains the list of keyboard layouts to be selected by the user. The value '1' is the default layout, which is loaded automatically in every process during the user's session. In Japanese and Korean versions of Windows, Microsoft's IME is the default.
Any keyboard layout, including an IME, is loaded in every process. KBDUS.DLL, the US keyboard layout DLL, contains only data and has no room to place any extra code. But an IME is a program that has several predetermined entries. No application can reject an IME; an IME acts like a system DLL. Before the instruction enters the entry point of an application, the IME file is loaded, thus DllEntry has been called.
Think of Japanese and Korean OSs. If the genuine Microsoft IME file is replaced with a hacked version using MoveFileEx or another method, the hacked IME will be loaded, even in the System process. This means the hacked IME can run even in the user sessions where the hacked IME is not the default keyboard layout.
A removal tool cannot repair or replace the IME files because the correct files differ depending on the OS and the Service Pack installed. If the hacked IME hinders the replacement of the files, it becomes even more difficult to fix the problem.
For those who don't use an IME, a simple IME that does not convert characters can be installed and become the default keyboard layout. There is a slight difference in UI, especially in the language bar, but most users will not understand why it happened unless they have experience using IMEs.
The average user would have no idea that the IME was running in his/her English OS. They might even search some commonly known registry load points for the culprit, but would likely find nothing.
It is far easier to develop a fake IME, which does not convert characters and does not display a window, than it is to infect or hack a genuine IME, or than to develop a whole new fully functional IME for Chinese, Japanese and Korean users.
In Korea, there seems to be no need to develop IMEs other than Microsoft's. But in Japan, because many users have become accustomed to their favourite input methods, multiple third-party IMEs are sold.
The image below shows the default display of the IME status bar on Japanese Windows XP.
When the icon of keyboard is clicked, the list of available IMEs in the current language is shown. If no IME is added, only one IME is displayed (MS-IME2002 here).
If two IME products are installed on the machine, the list will display three IMEs and their names. The IME currently in use is marked with a tick. (ATOK 2005 and Japanist 2003 are third-party Japanese IME products.)
The screenshot shown below is the dialog of the 'Regional and Language Options' Control Panel. The standard language or default language can be selected here. The standard language will be loaded automatically in every process in the session of the user who has selected the language.
The user also can remove an IME from the list.
As already discussed, the OS (IMM) always sends any keystrokes to the currently selected IME through ImeProcessKey and ImeToAsciiEx entry points in the IME file, which is just a DLL with the file extension 'IME'. It generally exports some mandatory entry points that should be called from the IMM.
The simplest way to log keystrokes is to log them in ImeProcessKey and return zero. ImeProcessKey receives virtual keys. If zero is returned, the IMM will no longer interact with IME for that keystroke. IME runs in every process and can act as if it is a part of any program.
If the IME sends out packets in the Internet Explorer process to the system or to monitoring tools, it appears as though Internet Explorer has sent them out, just like injecting such a routine into Internet Explorer.
Unlike the injection technique, however, an IME does not have to hook keyboard-related APIs or messages. Existing security tools would not detect such suspicious behaviour performed by the IME.
If ImeProcessKey returns a non-zero value, a virtual key is eventually passed to TranslateMessage by the application window procedure, then it is passed to the ImeToAsciiEx entry point to be converted to a character.
It is not surprising that IME is always loaded, even in Safe Mode. This means that the standard (default) IME cannot be deleted from the system, unless a user with a different default IME logs on.
The MoveFileEx API can be used to rename the IME in use. At the next login, the user will see the second IME in the list become his/her default IME.
Genuine non-infected IME files can be removed from the computer easily. However, if an IME is designed maliciously, code could be added, making the following possible:
It may be able to change the default IME of all the users.
Even if the user changes his/her default IME, IMEs that have already been selected cannot be changed. The user must re-login or reboot the computer. A program that forcefully changes the IME in use can be developed, but on NT-based Windows the IME module would remain in memory. If the IME runs a thread, it can keep running. And what if the thread checks the standard IME periodically and changes it back again?
MoveFileEx sets some registry values. If a malicious code deletes the values, it will be difficult to delete the IME file.
An IME is loaded even in 16-bit applications and the command prompt.
An IME can load WinSock, enabling it to access the Internet.
The following are some important entry points, or exported functions, that IMEs should have.
This is the start address of the IME DLL file. This is called when an IME file is loaded by the IMM during the initialization of an application if the IME is the default, or if it is loaded when the user selected the IME manually.
The IMM would call this entry at least once to retrieve the IME's properties. On NT-based Windows, this is called only once and the properties are stored in the global system memory.
An IME can set the member of lpInfo, among which fdwProperty has IME_PROP_UNICODE bit (0x00080000). If this bit is on, all the IME interfaces will become Unicode-based. If it is off, they become ANSI-based. All the interfaces are called from IMM, which would convert between Unicode and ANSI if the application does not match the code system.
An IME should set a string in lpszUIClass, such as 'MY_IME_UI_MAIN'. The IMM would automatically create the window of class 'MY_IME_UI_MAIN' at the time the application creates the first window. IME-related window messages are passed to this IME UI window. This window becomes one of the child windows of the application window. RegisterWindow should be called by the IME.
This is called when the IME is selected and unselected. If it is a standard IME, this is called after DllMain and before the entry point of the application is called.
hIMC is a handle to Input Context, in which an IME should store data.
This is called when the application is activated and deactivated.
The IMM calls this first to ask the IME whether this key stroke should be processed by the IME. If the IME returns false (zero), the IMM does not send this key to the ImeToAsciiEx.
If the MSB of lKeyData is on, the key is released, otherwise the key is pressed. lpbKeyState is a 256-byte matrix indicating whether each VKEY is down or up (including CAPS LOCK state, etc.). vKey is just a virtual key and needs the states of lpbKeyState to see what character is input.
If IME returns true (non-zero) in ImeProcessKey, the virtual key is passed to ImeToAsciiEx. This entry is the most important for the genuine IME; in this routine the IME converts alphabet, Katakana or Hangeul jamo (parts) to Chinese character, Hiragana, Kanji, Hangeul or Hanja. This routine determines the usability and performance of the IME.
IME should set the contents of lpTransBuf in order for a generated character to be input into the application window, otherwise the key stroke will be lost here. A genuine IME would send WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION and WM_IME_ENDCOMPOSITION sequentially.
A member hCompStr in hIMC has the composition string. hCompStr is a handle to the COMPOSITIONSTRING structure. A genuine IME should set members of dwCompStrLen, dwCompStrOffset, dwResultStrLen and dwResultStrOffset in COMPOSITIONSTRING and (especially dwCompStrLen and dwResultStrLen) may change at every key stroke.
If dwCompStrLen and dwCompStrOffset remain unchanged, the application is highly suspicious as an IME. dwCompStrLen is the length of the characters which are being converted and shown in a special IME UI window. dwResultStrLen is the length of the character string which is determined and input into the application window. Again, if dwCompStrLen is never greater than zero, but dwResultStrLen is greater than zero, the IME is highly suspect.
An IME that consults the web as to some information related to the input character strings could be developed as a product. But this action should only be initiated when the user performs a specific operation. There would be no legitimate purpose or value for sending all the keystrokes to the web. Similarly, there would be no legitimate reason for sending keystrokes input into a specific window.
If an IME always loads a socket library, there may be conflicts with the application. If the user runs a 16-bit version of an email client, the application would not run properly. Therefore (even though the number of users running a 16-bit Internet application is very low), any quality commercial product should avoid this.
Listening on a port should be avoided too, since it is hard to tell what port will be opened by the application. So, if you come across an IME that does this, beware, it could be a rogue one.
In the Far East versions of Windows used in China, Japan and Korea, genuine IMEs can be hacked and altered to log keystrokes or carry out malicious actions. The installer of the keylogger will replace an IME file and might set some IME-related registry values. Virus analysts must look out for either ImeProcessKey or ImeToAsciiEx entries that log keystrokes in a file or registry, send keystrokes through a socket, or do anything that is unnecessary as an IME functionality. A hacked IME would not behave any differently from the user's perspective.
In the other language versions of Windows, the keyboard layout can be changed to a fake IME which does nothing but log keystrokes, send the keystrokes or some other malicious behaviour. In this case users would notice the change in behaviour of the IME, but they are still able to input keys without problems. The installer will drop an IME file, add some IME-related registry values and change the registry of default keyboard layout. In this case, virus analysts should watch out for an ImeToAsciiEx entry that does almost nothing compared to what would be expected in a genuine IME.