工作原因,经常使用串口调试助手。虽然网上完全免费的串口调试助手很多,但是用习惯了友善,不想换其他的工具。
这个软件30天试用期过后,会不定时弹出注册提示窗口,有些烦人。于是想着去除掉。
因自己不会分析exe进行脱壳破解一类的,只有用偏门的思路,快速解决问题,忘坛友们不要嘲笑
主界面.jpg
二、针对上述注册提示窗口(模态窗口,本窗口不关闭,主窗口无法使用),我首先想到的方法是:使用系统的Findwindow和FindWindowEx函数,查找窗口,并调用SendMessage关闭。
按上述方法进行分析,
1.首先查看Findwindow和FindWindowEx函数的参数
[C#] 纯文本查看 复制代码
/// <summary>/// 查找窗口[/font][font="]/// </summary>/// <param name="lpClassName">窗口类名称</param>[/font][font="]/// <param name="lpWindowName">窗口标题名称</param>[/font][font="]/// <returns></returns>[DllImport("user32.dll", EntryPoint ="FindWindow")]private extern static IntPtr FindWindow(string lpClassName,string lpWindowName);/// <summary>/// 查找窗口[/font][font="]/// </summary>/// <param name="hwndParent">父窗体句柄</param>[/font][font="]/// <param name="hwndChildAfter">子窗体句柄</param>[/font][font="]/// <param name="strClass">窗口类名</param>[/font][font="]/// <param name="strWindow">窗口标题</param>[/font][font="]/// <returns></returns>[DllImport("user32.dll")]private extern static IntPtr FindWindowEx(IntPtr hwndParent,IntPtr hwndChildAfter,string strClass, string strWindow);
可以看出,调用这两个函数需要知道窗口的类名或者窗口标题名称。另外从助手界面可以看出两个窗口的名称都是一样的,看看两个窗口的类名是否一样,不一样的话,就可以直接查到这个窗口。使用VS自带的spy++查找窗口的句柄和类名,上图a.主窗口句柄
主窗体句柄.jpg
b.注册提示窗口句柄
注册提示窗体句柄1.jpg
从上面两个窗口可以得到的数据:
主窗口
句柄:000A0FAE(10进制:659374)
标题:友善串口调试助手
类:Qt5QWindowIcon
注册提示窗口
句柄:001E0FA2(10进制:1970082)
标题:友善串口调试助手
类:Qt5QWindowIcon
发现两个窗口的类名称也都一样,没办法直接按名称或类名关闭窗体了。
观察这两个窗口句柄,发现注册提示窗口的句柄值要大于主窗口的句柄值,仔细想想,除了第一次打开程序先弹出的注册提示窗口外,其它提示窗口的都是在主窗口出现之后出现的,后续提示窗口的句柄肯定大于主窗口的句柄,所以可以通过判断句柄值大小来确定哪个是我们的目标窗口。
2.实际上在提示窗口出现的情况下,多次调用FindWindow函数,每次获得的窗口句柄始终是弹出窗口的句柄,得不到主窗口的句柄;将FindWindow得到的弹出窗口句柄传入FindWindowEx的主窗体句柄参数位置可实现查找到主窗体。比较两个窗体的句柄大小,然后关闭弹出窗口句柄。
[C#] 纯文本查看 复制代码
private void StartDetect2() { string winname = "友善串口调试助手"; string winclsname = "Qt5QWindowIcon"; _ThreadDetect = new Thread(() => { while (true) { Application.DoEvents(); Thread.Sleep(1000); IntPtr hwnd1 =FindWindow(winclsname, winname); //通过窗体类名称和窗体名称查找句柄,精确过滤。如果只写窗口名称,也会查到文件夹名称为友善串口调试助手的窗口。影响后面的判断。 if(hwnd1!=IntPtr.Zero) { IntPtr hwnd2 =FindWindowEx(hwnd1, IntPtr.Zero, winclsname, winname); if(hwnd2!=IntPtr.Zero) { if (hwnd1.ToInt64()> hwnd2.ToInt64()) { SendMessage(hwnd1, 16, 0, 0);//关闭窗口,通过发送消息的方式 } else { SendMessage(hwnd2, 16, 0, 0);//关闭窗口,通过发送消息的方式 } } } } }); _ThreadDetect.Start(); }
3.上述方法试验后发现:只能在打开主窗口后,弹出注册提示窗之前,运行该方法才会有效,并且只能关闭一次弹出窗口。
分析原因,因为,弹出窗口出现后,FindWindow获取到的就是弹窗的句柄,该句柄值大于主窗口句柄,导致FindWindowEx查不到主窗口句柄。
三、通过上面的分析发现上面的思路达不到目的,于是考虑使用:查询桌面所有窗口句柄,然后将窗口名称和类名称都符合要求的窗口添加进一个List,然后再判断句柄大小,关闭大句柄的窗口。
直接上核心代码:
[C#] 纯文本查看 复制代码
usingSystem.Runtime.InteropServices;usingSystem.Threading;usingSystem.Diagnostics;usingSystem.IO;///<summary> /// 发送系统消息(可用来发送关闭窗口命令) /// </summary> /// <paramname="hWnd"></param> /// <paramname="msg"></param> /// <paramname="wParam"></param> /// <paramname="lParam"></param> /// <returns></returns> [DllImport("User32.dll",EntryPoint = "SendMessage")] private static extern intSendMessage(IntPtr hWnd, int msg, uint wParam, uint lParam); /// <summary> /// //用来遍历所有窗口 /// </summary> /// <paramname="lpEnumFunc"></param> /// <paramname="lParam"></param> /// <returns></returns> [DllImport("user32.dll")] private static extern boolEnumWindows(WNDENUMPROC lpEnumFunc, int lParam); /// <summary> /// 获取窗口Text /// </summary> /// <paramname="hWnd"></param> /// <paramname="lpString"></param> /// <paramname="nMaxCount"></param> /// <returns></returns> [DllImport("user32.dll")] private static extern intGetWindowTextW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]StringBuilderlpString, int nMaxCount); /// <summary> /// 获取窗口类名 /// </summary> /// <paramname="hWnd"></param> /// <paramname="lpString"></param> /// <paramname="nMaxCount"></param> /// <returns></returns> [DllImport("user32.dll")] private static extern intGetClassNameW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]StringBuilderlpString, int nMaxCount); /// <summary> /// 声明一个委托函数用于 Win32 API - EnumWindows 的回调函数: /// </summary> /// <paramname="hWnd"></param> /// <paramname="lParam"></param> /// <returns></returns> private delegate bool WNDENUMPROC(IntPtr hWnd,int lParam); //IntPtr hWnd用int也可以/// <summary> /// 自定义一个结构体,用来保存句柄信息 /// </summary> public struct WindowInfo { public IntPtr hWnd; public string szWindowName; public string szClassName; }/// <summary> /// 获取所有标题相同的窗体句柄及窗体类名 /// </summary> /// <paramname="windowText">窗体标题名称</param> /// <paramname="clsText">窗体类名称</param> /// <returns></returns> public List<WindowInfo>GetAllWindows(string windowText,string clsText) { //用来保存窗口对象 列表 List<WindowInfo> wndList =new List<WindowInfo>(); //enum all desktop windows EnumWindows(delegate(IntPtr hWnd, intlParam) { WindowInfo wnd = newWindowInfo(); StringBuilder sb = newStringBuilder(256); //get window name GetWindowTextW(hWnd, sb,sb.Capacity); wnd.szWindowName =sb.ToString(); //get window class GetClassNameW(hWnd, sb,sb.Capacity); wnd.szClassName =sb.ToString(); if((wnd.szWindowName==windowText)&& (wnd.szClassName==clsText)) { //get hwnd wnd.hWnd = hWnd; //add it into list wndList.Add(wnd); } return true; }, 0); return wndList; }Thread_ThreadDetect; //创建一个线程/// <summary> /// 开始检测 /// </summary> private void StartDetect() { string winname = "友善串口调试助手"; string winclsname ="Qt5QWindowIcon"; _ThreadDetect = new Thread(() => { while (true) { Application.DoEvents(); Thread.Sleep(1000); List<WindowInfo>winInfo = GetAllWindows(winname, winclsname); if (winInfo.Count >= 2) { long h1 =winInfo[0].hWnd.ToInt64(); long h2 =winInfo[1].hWnd.ToInt64(); if (h1 > h2) { Console.WriteLine("找到窗口,句柄为:"+ h1.ToString()); SendMessage(winInfo[0].hWnd, 16, 0, 0);//关闭窗口,通过发送消息的方式 } else { Console.WriteLine("找到窗口,句柄为:"+ h2.ToString()); SendMessage(winInfo[1].hWnd, 16, 0, 0);//关闭窗口,通过发送消息的方式 } } } });_ThreadDetect.IsBackground = true; _ThreadDetect.Start(); }private voidfrmCloseOtherForm_Load(object sender, EventArgs e) { //将本程序exe放在友善串口助手根目录,打开本程序自动打开友善串口助手 if(File.Exists("SerialPortUtility.exe")) { Process.Start("SerialPortUtility.exe"); } StartDetect(); this.ShowInTaskbar = false; }
主窗口.png
主窗口
图二:弹出窗口样式
弹窗.png
弹窗
主代码修改如下:
[C#] 纯文本查看 复制代码
private const int GWL_STYLE = (-16) ; //窗口样式/// <summary> /// 获得所属句柄窗体的样式函数 /// </summary> /// <param name="hWnd"></param> /// <param name="nIndex"></param> /// <returns></returns> [DllImport("user32.dll", SetLastError = true)] static extern int GetWindowLong(IntPtr hWnd, int nIndex);//获得所属句柄窗体的样式函数 /// <summary>/// 开始检测/// </summary>private void StartDetect(){ string winname = "友善串口调试助手"; //string winclsname = "Qt5QWindowIcon"; _ThreadDetect = new Thread(() => { while (true) { Application.DoEvents(); Thread.Sleep(500); List<WindowInfo> winInfo = GetAllWindows2(winname, "96CC0000"); if (winInfo.Count > 0) SendMessage(winInfo[0].hWnd, 16, 0, 0);//关闭窗口,通过发送消息的方式 } }); _ThreadDetect.IsBackground = true; _ThreadDetect.Start();}public List<WindowInfo> GetAllWindows2(string windowText, string clsText) { //用来保存窗口对象 列表 List<WindowInfo> wndList = new List<WindowInfo>(); //enum all desktop windows EnumWindows(delegate(IntPtr hWnd, int lParam) { WindowInfo wnd = new WindowInfo(); StringBuilder sb = new StringBuilder(256); //get window name GetWindowTextW(hWnd, sb, sb.Capacity); wnd.szWindowName = sb.ToString(); Int32 windowStyle = GetWindowLong(hWnd, -16); int value = int.Parse(clsText, NumberStyles.AllowHexSpecifier); if ((wnd.szWindowName == windowText) && (windowStyle == value)) { //get hwnd wnd.hWnd = hWnd; wndList.Add(wnd); } return true; }, 0); return wndList; }