在使用某抢票软件的时候,由于要设置抢票成功后的通知,就用了QQ通知。结果发现当QQ在锁定时,也可以获取到QQ的控制面板进行操作,竟然绕过了QQ的锁定,于是就去尝试如何实现QQ锁定的绕过。

虚线阴影分割线

情景还原


首先演示一下如何使用抢票软件进行QQ锁定的绕过。这里以最新版本9.0.7(24121)为例。下载后正常安装。

1542639823372

安装后查看版本。

1542640660482

将QQ锁定,然后打开软件,选择QQ通知,点击“获取聊天窗口”,此时会进行QQ窗体的加载,然后在“要发送的窗口”下拉框会显示一个随机字符串,点击“测试发送”按钮,会发现QQ控制面板弹出。

1542641095561

而且是可以进行未锁定前的所有功能。如发送和接收消息,如图所示。

1542720813184

实现过程

刚开始的想法是首先获取QQ窗口的句柄,然后再利用句柄进行显示与隐藏。

尝试一

这里采用的是精易编程助手来获取句柄,当qq处于非锁定状态,获取QQ窗体句柄后,可以利用句柄对QQ进行隐藏或显示,即使把QQ锁定,也可以进行隐藏或者显示。

1542722213724

但是当QQ隐藏后,此时再获取句柄后,无法绕过QQ的锁定。这时候获取的句柄为QQ锁定状态的句柄。

1542722213724

该方法只能对未锁定的QQ进行操作,锁定后就无法获取控制面板句柄了,因此该方法行不通。

尝试二

放弃了精易编程助手,采用更专业的工具spy++。找到句柄后发现标题为一串随机的字符串,与抢票工具获取聊天窗口中标题一样。

1542724530787

猜测需要最终需要获取的句柄就是这个,句柄对应的十六进制为504BE,然后调用windows api进行窗口的显示和隐藏。

显示窗口ShowWindow (句柄, 1),隐藏窗口ShowWindow (句柄, 0)。

效果如图所示。

1542724530787

这样虽然能够满足,但是却不够智能,无法自动获取句柄,需要借助其他工具去寻找句柄,因此放弃。

尝试三

本次测试中走了两条“弯路”,在这里简要说明一下,就不再附详细的过程了。某次发现使用

1
2
临时_句柄=FindWindowA (“TXGuiFoundation”, “TXMenuWindow”)
句柄=窗口_取父句柄 (窗口_取父句柄 (临时_句柄))

或者

1
2
临时_句柄=FindWindowA (“TXGuiFoundation”, “”)
句柄=窗口_取父句柄 (临时_句柄)

可以获取到QQ锁定时控制面板的句柄。如图所示

1542724530787

但这些只是概率事件,多数情况下还是不行的。即使可以使用这两种方法,但是这两种方法获取的都是一个句柄,当有两个QQ时,就无法同时获取两个句柄了,所以该方法不可取。

由于QQ处于锁定时,窗口标题为随机的字符串,所以无法使用FindWindowA这种方法了。

尝试四之最终实现

此时想到另外一个思路,遍历屏幕上所有的顶层窗口,然后根据条件进行筛选。

这里用的Java来实现,操作Windows API采用了JNA

JNA的全称是Java Native Access,你只要在一个java接口中描述本地库中的函数与结构, JNA将在运行期动态访问本地库,自动实现Java接口到本地库函数的映射。

使用maven加入所需的依赖。

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.1.0</version>
</dependency>

<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.1.0</version>
</dependency>

枚举屏幕上所有的顶层窗口采用的是EnumWindows

1
2
boolean EnumWindows(WinUser.WNDENUMPROC lpEnumFunc,
Pointer data)

该函数枚举屏幕上所有的顶层窗口,并将窗口句柄传送给应用程序定义的回调函数。回调函数返回FALSE将停止枚举,否则EnumWindows函数继续到所有顶层窗口枚举完为止。

参数:lpEnumFunc - 指向应用程序定义的回调函数的长指针。

参数:data - 指定要传递给回调函数的应用程序定义的值。

代码实现如下:

1
2
3
4
5
6
7
User32.INSTANCE.EnumWindows(new WinUser.WNDENUMPROC() {

public boolean callback(WinDef.HWND hwnd, Pointer pointer) {
System.out.println(hwnd);
return true;
}
},null);

1542765670181

此时获取的时所有顶层窗口的句柄,剩下的就是去如何进行过滤,留下所需的句柄。

1542766813276

通过对比发现,可以用窗口样式进行区分。当QQ锁定时(或QQ未锁定且最小化时),对应的窗口样式为860C0000(十进制为2248933376)。

下面为QQ锁定时的样式。

1542766908014

通过查询API,可以用User32.INSTANCE.GetWindowLongPtr(hwnd,User32.GWL_STYLE)来获取窗口样式。然后通过这种方法进行过滤,当该值为2248933376,则认为对应的hwnd为QQ控制面板的句柄。

主要代码如下:

1
2
3
4
5
6
7
8
9
10
User32.INSTANCE.EnumWindows(new WinUser.WNDENUMPROC() {

public boolean callback(WinDef.HWND hwnd, Pointer pointer) {

if(User32.INSTANCE.GetWindowLongPtr(hwnd,User32.GWL_STYLE).longValue()==2248933376L){
System.out.println(hwnd);
}
return true;
}
},null);

1542767244090

可以看到成功获取了QQ锁定时控制面板的句柄。有了句柄,就可以进行之前的操作了,如显示与隐藏。

当QQ未锁定时,QQ弹出时对应的窗口样式为960c0000(十进制为2517368832)。

1542768137649

此时就可以先通过EnumWindows枚举所有的窗口,然后再使用GetWindowLongPtr(hwnd,User32.GWL_STYLE)获取窗口样式进行匹配,匹配到所需的窗口。

这里将QQ锁定和未锁定的情况都考虑进去,然后获取QQ控制面板的句柄。之后通过GetWindowText获取窗口的标题,再使用ShowWindow进行隐藏与显示操作。主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) throws Exception{

List<WinDef.HWND> lt = getQQHwnds();
for (WinDef.HWND hwnd:lt) {
char[] titleBuffer = new char[512];
User32.INSTANCE.GetWindowText(hwnd,titleBuffer,512);
System.out.println(new String(titleBuffer));
User32.INSTANCE.ShowWindow(hwnd,1);//显示
Thread.sleep(2000);
User32.INSTANCE.ShowWindow(hwnd,0);//隐藏
Thread.sleep(2000);
User32.INSTANCE.ShowWindow(hwnd,1);//显示
Thread.sleep(2000);

}
}

public static List<WinDef.HWND> getQQHwnds(){
final List<WinDef.HWND> lt = new ArrayList<WinDef.HWND>();

User32.INSTANCE.EnumWindows(new WinUser.WNDENUMPROC() {
public boolean callback(WinDef.HWND hwnd, Pointer pointer) {
if(User32.INSTANCE.GetWindowLongPtr(hwnd,User32.GWL_STYLE).longValue()==2517368832L||User32.INSTANCE.GetWindowLongPtr(hwnd,User32.GWL_STYLE).longValue()==2248933376L){
lt.add(hwnd);
}
return true;
}
},null);
return lt;
}

首先获取到QQ控制面板的句柄,然后依次获取其标题,并进行显示-隐藏-显示等过程。效果如图所示。

1542768137649

这种成功实现了QQ锁定时自动获取控制面板句柄,实现了QQ锁定的绕过。而且有多个QQ时,都可以获取其句柄。

总结

在一次偶然的情况下可以绕过QQ锁定,于是就进行了尝试,走了一些弯路,最终实现了这种效果。主要过程就是获取所有顶层窗口句柄-根据窗口样式匹配到QQ控制面板的句柄-使用ShowWindow进行显示。也许这不是最佳的解决方法。如果大家有更好的方法或者建议,欢迎分享。

参考资料

[1]https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowlonga

[2]https://java-native-access.github.io/jna/4.2.0/com/sun/jna/platform/win32/User32.html#EnumWindows-com.sun.jna.platform.win32.WinUser.WNDENUMPROC-com.sun.jna.Pointer-