ExtTextOut and DrawText

Jan 29, 2009 at 3:13 PM
I've been trying to hook ExtTextOut and DrawText using managed hooking with EasyHook.

The hooking procedure works fine now, but when I hook the text functions the text in the target applications gets garbled, even if I only call the original ExtTextOut and DrawText functions in my detours...




 [UnmanagedFunctionPointer(CallingConvention.StdCall,
          CharSet = CharSet.Auto,
           SetLastError = true)]
        delegate bool DExtTextOutW(
            IntPtr hdc,
            int X,
            int y,
            uint fuOptions,
            [In] ref RECT lprc,
            string lpString,
            uint cbCount,
            [In] int[] lpDx
            );

        // just use a P-Invoke implementation to get native API access from C# (this step is not necessary for C++.NET)
        [DllImport("gdi32.dll",
           CharSet = CharSet.Auto,
            SetLastError = true,
            CallingConvention = CallingConvention.StdCall)]
        static extern bool ExtTextOutW(IntPtr hdc,
                                       int X,
                                       int Y,
                                       uint fuOptions,
                                       [In] ref RECT lprc,
                                       string lpString,
                                       uint cbCount,
                                       [In] int[] lpDx);

        // this is where we are intercepting all file accesses!
        static bool ExtTextOutW_Hooked(
            IntPtr hdc,
            int X,
            int Y,
            uint fuOptions,
            [In] ref RECT lprc,
            string lpString,
            uint cbCount,
            [In] int[] lpDx)
        {        

            // call original API...
            return ExtTextOutW(
                 hdc,
                 X,
                 Y,
                 fuOptions,
                 ref lprc,
                 lpString,
                 cbCount,
                 lpDx
                  );

        }


I would attach a screen shot of the results here, but it doesn't seem to be supported here?

Anyways. Have anyone else tried this before? Do I need to know anything specific when detouring such functions with managed hooks?

Regards,
Tomas
Jan 30, 2009 at 5:56 AM
is this the pic?
http://www.experts-exchange.com/images/63402/detours1.jpg

for the string have you tried [MarshalAs(UnmanagedType.LPTStr)] or LPWStr?

I'm not great at interop, so I may be off base here, but I would also try changing lpstring to a UIntPtr and see if your hooked process still garbles text.


Jan 30, 2009 at 8:38 AM
Hello and thanks for the response,

The image you linked to doesn't seem to show the exact same behaviour. In your example the characters seem to be other characters than they should be. In my hooked application the text is mushed up in small clusters almost:
Firefox hooked

It has the same behaviour in Notepad.

I tried to marshal the string as you mentioned and changed the type to uintptr without any effect. Oddly enough. I'll probably have to look into interop and GDI?

Thanks!
Jan 30, 2009 at 10:51 AM
I figured it out. It was the character spacing parameter that was defined wrong...

I changed it from int[] to IntPtr, and it now works as it should...duh...

Thanks though!
Oct 6, 2009 at 5:48 AM

Hello,

I've been trying to hook ExtTextOut but the text (lpString) I get from the firefox is "scrambled". I've changed int[] to IntPtr but the process still garbles text result.

Queue console result:

[ExtTextOut] [848:340]: "2XWLOVSDJHVLWpQJOLVKHVWGLVSRQLEOHHQ??? nCount 6"
[ExtTextOut] [848:340]: "0DUTXHSDJHVLWpQJOLVKHVWGLVSRQLEOHHQ??? nCount 12"
[ExtTextOut] [848:340]: "+LVWRULTXHDOLWpQJOLVKHVWGLVSRQLEOHHQ??? nCount 10"
[ExtTextOut] [848:340]: "$IILFKDJHLDOLWpQJOLVKHVWGLVSRQLEOHHQ??? nCount 9"
[ExtTextOut] [848:340]: "eGLWLRQHWLDOLWpQJOLVKHVWGLVSRQLEOHHQ??? nCount 7"
[ExtTextOut] [848:340]: ")LFKLHUHWLDOLWpQJOLVKHVWGLVSRQLEOHHQ??? nCount 7" 

 Any thought?

My code below:

  delegate bool DExtTextOut(IntPtr hdc,
                                       int X,
                                       int Y,
                                       uint fuOptions,
                                       [In] ref RECT lprc,
                                       string lpString,
                                       uint cbCount,
                                       [In] IntPtr lpDx);

 [DllImport("gdi32.dll",
            CharSet = CharSet.Auto,
            SetLastError = true,
            CallingConvention = CallingConvention.StdCall)]
        static extern bool ExtTextOut(IntPtr hdc, int X, int Y, uint fuOptions, [In] ref RECT lprc, string lpString, uint cbCount, [In] IntPtr lpDx);

  static bool ExtTextOut_Hooked(IntPtr hdc, int X, int Y, uint fuOptions, [In] ref RECT lprc, string lpString, uint cbCount, [In] IntPtr lpDx)
        {

            try
            {
                Main This = (Main)HookRuntimeInfo.Callback;

                lock (This.Queue)
                {
                    This.Queue.Push("[ExtTextOut] [" + RemoteHooking.GetCurrentProcessId() + ":" +
                    RemoteHooking.GetCurrentThreadId() + "]: \"" + lpString + " nCount " + cbCount.ToString() + "\"");                }
            }
            catch
            {
            }

            return ExtTextOut(hdc, X, Y, fuOptions, ref lprc, lpString, cbCount, lpDx);           

        }

Thank you in advance for your help

 

 Attilio

Belgium

Oct 6, 2009 at 7:59 AM

Check the fuOptions for the ETO_GLYPH_INDEX flag. If it is set, you need to convert glyphs back into characters.

http://msdn.microsoft.com/en-us/library/dd162713(VS.85).aspx

 

Good luck :)

 

Regards,

Tomas

Jan 28, 2010 at 2:51 AM

  yes , I meet the same  trobule .

 

Thank you in advance for your help

 

 

 xiangwei.ma

Jun 29, 2010 at 4:16 AM

Hi there, I had the same problem with scrambled text. Anyone has idea what is the API used for converting glyphs back into characters?

Jul 15, 2010 at 12:05 PM

Anyone has found a way to convert glyphs back to characters?

 

Jan 16, 2011 at 5:58 PM

Hi all.

I also try to hook this function but have no progress in it. I modified FileMon example to hook ExtTextOutW function.

namespace FileMonInject
{
    public class Main : EasyHook.IEntryPoint
    {
        FileMon.FileMonInterface Interface;
        LocalHook ExtTextOutHook;
        Stack<String> Queue = new Stack<String>();

        public Main(
            RemoteHooking.IContext InContext,
            String InChannelName)
        {
            // connect to host...
            Interface = RemoteHooking.IpcConnectClient<FileMon.FileMonInterface>(InChannelName);

            Interface.Ping();
        }

        public void Run(
            RemoteHooking.IContext InContext,
            String InChannelName)
        {
            // install hook...
            try
            {
                ExtTextOutHook = LocalHook.Create(
                    LocalHook.GetProcAddress("Gdi32.dll", "ExtTextOutW"),
                    new DExtTextOutW(ExtTextOutW_Hooked),
                    this);

                ExtTextOutHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });
            }
            catch (Exception ExtInfo)
            {
                Interface.ReportException(ExtInfo);

                return;
            }

            Interface.IsInstalled(RemoteHooking.GetCurrentProcessId());

            RemoteHooking.WakeUpProcess();

            // wait for host process termination...
            try
            {
                while (true)
                {
                    Thread.Sleep(500);

                    // transmit newly monitored file accesses...
                    if (Queue.Count > 0)
                    {
                        String[] Package = null;

                        lock (Queue)
                        {
                            Package = Queue.ToArray();

                            Queue.Clear();
                        }

                        Interface.OnCreateFile(RemoteHooking.GetCurrentProcessId(), Package);
                    }
                    else
                        Interface.Ping();
                }
            }
            catch
            {
                // Ping() will raise an exception if host is unreachable
            }
        }

        [UnmanagedFunctionPointer(CallingConvention.StdCall,
         CharSet = CharSet.Auto,
          SetLastError = true)]
        delegate bool DExtTextOutW(
            IntPtr hdc,
            int X,
            int y,
            uint fuOptions,
            [In] ref IntPtr lprc,
            string lpString,
            uint cbCount,
            [In] int[] lpDx
            );

        // just use a P-Invoke implementation to get native API access from C# (this step is not necessary for C++.NET)
        [DllImport("gdi32.dll",
           CharSet = CharSet.Auto,
            SetLastError = true,
            CallingConvention = CallingConvention.StdCall)]
        static extern bool ExtTextOutW(IntPtr hdc,
                                       int X,
                                       int Y,
                                       uint fuOptions,
                                       [In] ref IntPtr lprc,
                                       string lpString,
                                       uint cbCount,
                                       [In] int[] lpDx);

        // this is where we are intercepting all file accesses!
        static bool ExtTextOutW_Hooked(
            IntPtr hdc,
            int X,
            int Y,
            uint fuOptions,
            [In] ref IntPtr lprc,
            string lpString,
            uint cbCount,
            [In] int[] lpDx)
        {

            try
            {
                Main This = (Main)HookRuntimeInfo.Callback;

                lock (This.Queue)
                {
                    This.Queue.Push("[" + RemoteHooking.GetCurrentProcessId() + ":" +
                        RemoteHooking.GetCurrentThreadId() + "]: \"" + lpString + "\"");
                }
            }
            catch
            {
            }

            // call original API...
            return ExtTextOutW(
                 hdc,
                 X,
                 Y,
                 fuOptions,
                 ref lprc,
                 lpString,
                 cbCount,
                 lpDx
                  );

        }
    }
}

 

FilMon.cs is the same as in FileMon example.

I'm running this tool with 2 applications to hooked: notepad and firefox. Neither of them are hook. Nothing happens: no error, filemon is still working and doesn't hook anything. What do I do wrong?

Thank you.

Aug 16, 2011 at 6:51 AM
Edited Aug 16, 2011 at 6:56 AM

Hi
 
I wanted to hooking ExtTextOut with easyhook

everything is ok and I don't have any error but
the problem is this:
 
I opened a notepad 
then I ran my project
but
when I clicked on the notepad
it came to close !!! I don't know why!!!!
 
Please help me
 
Filemon program.cs :

using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Text;
using System.IO;
using System.Diagnostics;
using EasyHook;
 
namespace FileMon
{
    public class FileMonInterface : MarshalByRefObject
    {
        public void IsInstalled(Int32 InClientPID)
        {
            Console.WriteLine("FileMon has been installed in target
{0}.\r\n", InClientPID);
        }
 
        public void OnCreatFile(Int32 InClientPID, String[] InFileNames)
        {
            for (int i = 0; i < InFileNames.Length; i++)
            {
                Console.WriteLine(InFileNames[i]);
            }
        }
 
        public void ReportException(Exception InInfo)
        {
            Console.WriteLine("The target process has reported an
error:\r\n" + InInfo.ToString());
        }
 
        public void Ping()
        {
        }
    }
 
    class Program
    {
        static String ChannelName = null;
 
        static void Main(string[] args)
        {
            Int32 TargetPID = 0;
 
            if ((args.Length != 1) || !Int32.TryParse(args[0], out
TargetPID))
            {
                Console.WriteLine();
                Console.WriteLine("Usage: FileMon %PID%");
                Console.WriteLine();
 
              // return;
            }
 
            try
            {
                Config.Register(
                    "A FileMon like demo application.",
                    "FileMon.exe",
                    "FileMonInject.dll");
 
                RemoteHooking.IpcCreateServer<FileMonInterface>(ref
ChannelName, WellKnownObjectMode.SingleCall);
 
                Process[] ProcList = Process.GetProcesses();
                String FileName;
                Int32 Id;
                for (int i = 0; i < ProcList.Length; i++)
                {
                    Process Proc = ProcList[i];
                    FileName = Proc.ProcessName;
                    Id = Proc.Id;
                    if (FileName == "notepad")
                        TargetPID = Id;
                }
 
                RemoteHooking.Inject(
                    TargetPID,
                    "FileMonInject.dll",
                    "FileMonInject.dll",
                    ChannelName);
                
                Console.ReadLine();
            }
            catch (Exception ExtInfo)
            {
                Console.WriteLine("There was an error while connecting to
target:\r\n{0}", ExtInfo.ToString());
                Console.ReadLine();
            }
        }
    }
}

FileMonInject Main.cs :
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using EasyHook;
 
namespace FileMonInject
{
    public class Main : EasyHook.IEntryPoint
    {
        FileMon.FileMonInterface Interface;
        LocalHook ExtTextOutHook;
        Stack<String> Queue = new Stack<String>();
 
        public Main(
            RemoteHooking.IContext InContext,
            String InChannelName)
        {
            // connect to host...
            Interface =
RemoteHooking.IpcConnectClient<FileMon.FileMonInterface>(InChannelName);
 
            Interface.Ping();
        }
 
        public void Run(
            RemoteHooking.IContext InContext,
            String InChannelName)
        {
            // install hook...
            try
            {
 
                ExtTextOutHook = LocalHook.Create(
                    LocalHook.GetProcAddress("gdi32.dll", "ExtTextOutW"),
                    new DExtTextOut(ExtTextOut_Hooked), this);
 
                ExtTextOutHook.ThreadACL.SetExclusiveACL(new Int32[] { 0
});
            }
            catch (Exception ExtInfo)
            {
                Interface.ReportException(ExtInfo);
 
                return;
            }
 
            Interface.IsInstalled(RemoteHooking.GetCurrentProcessId());
 
            RemoteHooking.WakeUpProcess();
 
            // wait for host process termination...
            try
            {
                while (true)
                {
                    Thread.Sleep(500);
 
                    // transmit newly monitored file accesses...
                    if (Queue.Count > 0)
                    {
                        String[] Package = null;
 
                        lock (Queue)
                        {
                            Package = Queue.ToArray();
 
                            Queue.Clear();
                        }
 
                      
Interface.OnCreatFile(RemoteHooking.GetCurrentProcessId(), Package);
                    }
                    else
                        Interface.Ping();
                }
            }
            catch
            {
                // Ping() will raise an exception if host is unreachable
            }
        }
 
        [Serializable, StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
 
            public RECT(int left_, int top_, int right_, int bottom_)
            {
                Left = left_;
                Top = top_;
                Right = right_;
                Bottom = bottom_;
            }
 
            public int Height { get { return Bottom - Top; } }
            public int Width { get { return Right - Left; } }
 
        }
      /* [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            private int _Left;
            private int _Top;
            private int _Right;
            private int _Bottom;
        }*/
 

 
        [UnmanagedFunctionPointer(CallingConvention.StdCall,
            CharSet = CharSet.Unicode,
            SetLastError = true)]
        delegate bool DExtTextOut(
            IntPtr hdc,
            int X,
            int Y,
            uint fuOptions,
            [In] ref RECT lprc,
            [MarshalAs(UnmanagedType.LPWStr)] string lpString,
            uint cbCount,
            [In] IntPtr lpDx); 
            /*IntPtr DCreateFile
            (String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);*/
 
        // just use a P-Invoke implementation to get native API access
from C# (this step is not necessary for C++.NET)
        [DllImport("gdi32.dll",
            CharSet = CharSet.Unicode,
            SetLastError = true,
            CallingConvention = CallingConvention.StdCall)]
        static extern bool ExtTextOut(
            IntPtr hdc,                                        
            int X,                                        
            int Y,                                        
            uint fuOptions,                                        
            [In] ref RECT lprc,
            [MarshalAs(UnmanagedType.LPWStr)] string lpString,            
                          
            uint cbCount,                                        
            [In] IntPtr lpDx); 
          /*IntPtr CreateFile String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);*/
 
        // this is where we are intercepting all file accesses!
        static bool ExtTextOut_Hooked(
            IntPtr hdc,            
            int X,            
            int Y,            
            uint fuOptions,            
            [In] ref RECT lprc,
            [MarshalAs(UnmanagedType.LPWStr)] string lpString,            
 
            uint cbCount,            
            [In] IntPtr lpDx) 
            /*IntPtr CreateFile_Hooked(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile)*/
        {
            
            try
            {
                Main This = (Main)HookRuntimeInfo.Callback;
 
                lock (This.Queue)
                {
                  
                //if (lpString == "File")
                //    lpString = "hello";
                  //This.Queue.Push(lpString);
                    This.Queue.Push("[" +
RemoteHooking.GetCurrentProcessId() + ":" +
                      RemoteHooking.GetCurrentThreadId() + "]: \"" +
lpString + "\"");
                  
                }
            }
            catch
            {
                Console.WriteLine("OOoops");
            }
 
            // call original API...
            return ExtTextOut(
                hdc,                  
                X,                  
                Y,                  
                fuOptions,                  
                ref lprc,                  
                lpString,                  
                cbCount,                  
                lpDx); 
                /*InFileName,
                InDesiredAccess,
                InShareMode,
                InSecurityAttributes,
                InCreationDisposition,
                InFlagsAndAttributes,
                InTemplateFile);*/
        }
    }
}

I opened a notepad
then I ran my project
I saw this in command window:
 
Usage: FileMon %PID%
FileMon has been installed in target 14896.
 

and when i clicked on notepad , it came closed
 
and I see this in command window:
 
[14896:16960]: ""