【Frida】 08_将目标窗口切换到前台

🛫 系列文章导航

🛫 导读

开发环境

版本号描述
文章日期2024-03-17
操作系统Win11 - 22H222621.2715
node -vv20.10.0
npm -v10.2.3
yarn -v3.1.1
frida-compile10.2.1高版本各种异常
扫雷程序下载地址https://download.csdn.net/download/kinghzking/88979919
课程源码https://gitcode.net/kinghzking/MyOpen所在目录:/course/frida

1️⃣ 实现方案

在Windows编程中,GUI界面编程经过二三十年的发展,产生了大量API,庞杂无比,应用场景各异,这里简单记录下。
可以使用以下方法将窗口切换到前台:

SetForegroundWindow

SetForegroundWindow函数:SetForegroundWindow函数可将指定窗口设置为前台窗口。以下是一个简单的示例代码:

#include <windows.h>

int main()
{
    HWND hWnd = FindWindow(NULL, "Window Title");
    if (hWnd != NULL)
    {
        SetForegroundWindow(hWnd);
    }
    
    return 0;
}

SwitchToThisWindow

SwitchToThisWindow函数:SwitchToThisWindow函数可以将指定窗口立即切换到前台而不改变焦点。示例代码如下:

#include <windows.h>

int main()
{
    HWND hWnd = FindWindow(NULL, "Window Title");
    if (hWnd != NULL)
    {
        SwitchToThisWindow(hWnd, TRUE);
    }
    
    return 0;
}

AttachThreadInput

AttachThreadInput函数:通过AttachThreadInput函数将目标窗口所在线程的输入处理过程与当前线程的输入处理过程连接起来,从而实现将窗口切换到前台。示例代码如下:

#include <windows.h>

int main()
{
    HWND hWnd = FindWindow(NULL, "Window Title");
    if (hWnd != NULL)
    {
        DWORD dwThisThreadId = GetCurrentThreadId();
        DWORD dwTargetThreadId = GetWindowThreadProcessId(hWnd, NULL);
        
        AttachThreadInput(dwThisThreadId, dwTargetThreadId, TRUE);
        SetForegroundWindow(hWnd);
        AttachThreadInput(dwThisThreadId, dwTargetThreadId, FALSE);
    }
    
    return 0;
}

SetActiveWindow

SetActiveWindow函数:使用SetActiveWindow函数可以将指定窗口设置为活动窗口(即前台窗口)。示例代码如下:

#include <windows.h>

int main()
{
    HWND hWnd = FindWindow(NULL, "Window Title");
    if (hWnd != NULL)
    {
        SetActiveWindow(hWnd);
    }
    
    return 0;
}

BringWindowToTop

BringWindowToTop函数:BringWindowToTop函数可以将指定窗口切换到Z轴的顶部,但并不会将窗口设置为焦点窗口。示例代码如下:

#include <windows.h>

int main()
{
    HWND hWnd = FindWindow(NULL, "Window Title");
    if (hWnd != NULL)
    {
        BringWindowToTop(hWnd);
    }
    
    return 0;
}

SetWindowPos

SetWindowPos函数:使用SetWindowPos函数可以设置窗口的位置和大小,并将其放置在Z顺序的指定位置,从而将窗口切换到前台。示例代码如下:

#include <windows.h>

int main()
{
    HWND hWnd = FindWindow(NULL, "Window Title");
    if (hWnd != NULL)
    {
        SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
    }
    
    return 0;
}

ShowWindow

ShowWindow函数:通过ShowWindow函数可以显示指定窗口,并将其设为活动窗口,从而将窗口切换到前台。示例代码如下:

#include <windows.h>

int main()
{
    HWND hWnd = FindWindow(NULL, "Window Title");
    if (hWnd != NULL)
    {
        ShowWindow(hWnd, SW_SHOW);
        SetActiveWindow(hWnd);
    }
    
    return 0;
}

PostMessage

PostMessage函数:通过发送WM_SYSCOMMAND消息将窗口最大化,然后再恢复窗口大小可以将窗口切换到前台。有点不太实用
示例代码如下:

#include <windows.h>

int main()
{
    HWND hWnd = FindWindow(NULL, "Window Title");
    if (hWnd != NULL)
    {
        PostMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE, 0);
        PostMessage(hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
    }
    
    return 0;
}

2️⃣ 编码实现

上面讲述了大量的窗口换到前台的API,独立的API并不能保证程序的正常,这里使用部分接口组合实现

kernel32.ts封装

之前我们用的都是User32.dll模块导出函数,现在需要引入Kernel32.dll模块的导出函数,因此增加kernel32.ts模块。

封装和引用,同User32.ts一样的,不再赘述。

生成NativeFunction封装

由于代码中大量引入NativeFunction对象,为了更好的编码,我们将其进行封装EZ生成NativeFunction
该函数用到了两个frida方法:Module.findExportByNamenew NativeFunction,我们将这两个函数的参数全部作为我们的函数EZ生成NativeFunction的参数,其中abiOrOptions、moduleName都有默认值,以此减少参数的传递。

最终函数如下所示:

/*
@param exportName - 导出函数名
@param retType - 返回值类型
@param argTypes - 参数类型数组
@param abiOrOptions - ABI类型或者NativeFunctionOptions类型
@param moduleName — 模块名或者路径默认为"Kernel32.dll"
*/
function EZ生成NativeFunction(exportName: string, 
    retType: NativeFunctionReturnType,
    argTypes: [] | NativeFunctionArgumentType[], 
    abiOrOptions: NativeABI | NativeFunctionOptions = "default",
    moduleName: string = "Kernel32.dll",
  ) {
  let address = Module.findExportByName(moduleName, exportName);
  return new NativeFunction(address!, retType, argTypes, abiOrOptions);
}

win api函数封装

我们以GetCurrentThreadId函数为例,说明新的函数优势:

  • 静态属性static func_GetCurrentThreadId,保证只获取一次。
  • 静态win api static GetCurrentThreadId,方便外部模块直接调用。
  • 直接调用,将返回值返回,外部不再关系内部实现(完全封装了frida)。
export default class Kernel32 {
  // DWORD GetCurrentThreadId();
  // 静态属性,保证只获取一次。
  private static func_GetCurrentThreadId: AnyFunction;
  // 静态win api ,方便外部模块直接调用。
  static GetCurrentThreadId(): number {
      if (this.func_GetCurrentThreadId == null) {
        this.func_GetCurrentThreadId = EZ生成NativeFunction("GetCurrentThreadId", "int", []);
      }
      // 直接调用,将返回值返回,外部不再关系内部实现(完全封装了frida)。
      return this.func_GetCurrentThreadId();
  }
}

其它api封装类似。针对user32中的函数,目前使用旧的方式,代码参考《https://gitcode.net/kinghzking/MyOpen》。

将目标窗口切换到前台

针对扫雷,直接调用User32.SetWindowPos,传参HWND_TOPMOST就能置顶了。
但是小编又对另一款C#游戏做了测试,发现需要使用User32.AttachThreadInput之后,再调用User32.SetWindowPos等函数。
所以,为了保险起见,我们将各种实现混合到一起,最终代码如下所示:

  将目标窗口切换到前台() {
    let hForeWnd = User32.GetForegroundWindow();
    let dwCurID = Kernel32.GetCurrentThreadId();
    let dwForeID = User32.GetWindowThreadProcessId(hForeWnd, ptr(0));
    User32.AttachThreadInput(dwCurID, dwForeID, 1);

    User32.ShowWindow(this.hWnd, User32.Const.SW_RESTORE);
    User32.SetForegroundWindow(this.hWnd)
    User32.SetWindowPos(this.hWnd, User32.Const.HWND_TOPMOST, 0, 0, 0, 0, User32.Const.SWP_NOSIZE | User32.Const.SWP_NOMOVE);
    User32.SetWindowPos(this.hWnd, User32.Const.HWND_NOTOPMOST, 0, 0, 0, 0, User32.Const.SWP_NOSIZE | User32.Const.SWP_NOMOVE);

    User32.AttachThreadInput(dwCurID, dwForeID, 0);
  }

🛬 文章小结

关于逆向相关的内容,很多时候,最终还是得对正向开发有更深入的理解才行。
就像本篇的内容,只有针对GUI有了一定的积累才能驾熟就轻,快速理解。

ps: 文章中内容仅用于技术交流,请勿用于违规违法行为。

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜猫逐梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值