🛫 系列文章导航
- 【Frida】 00_简单介绍和使用 https://blog.csdn.net/kinghzking/article/details/123225580
- 【Frida】 01_食用指南 https://blog.csdn.net/kinghzking/article/details/126849567
- 【Frida】 03_初识frida-node https://blog.csdn.net/kinghzking/article/details/136685316
- 【Frida】 04_Frida中使用TypeScript脚本(采坑) https://blog.csdn.net/kinghzking/article/details/136772475
- 【Frida】 05_读取扫雷游戏的数据 https://blog.csdn.net/kinghzking/article/details/136781623
- 【Frida】 06_分析扫雷游戏的数据,显示地雷位置 https://blog.csdn.net/kinghzking/article/details/136685316
- 【Frida】 07_让系统重新绘制指定窗口 https://blog.csdn.net/kinghzking/article/details/136829854
- 【Frida】 08_将目标窗口切换到前台 https://blog.csdn.net/kinghzking/article/details/136837275
- 【Frida】 09_获取软件窗口位置,设置鼠标指针位置 https://blog.csdn.net/kinghzking/article/details/136854052
- 【Frida】10_用鼠标自动标记棋盘上的雷区(一键过关) https://blog.csdn.net/kinghzking/article/details/136854020
- 【frida-实战】“一行”代码教你获取WeGame平台中所有的lua脚本 https://blog.csdn.net/kinghzking/article/details/125590584
- 【Frida-实战】EA游戏平台的文件监控(PsExec.exe提权) https://blog.csdn.net/kinghzking/article/details/130512479
🛫 导读
开发环境
版本号 | 描述 | |
---|---|---|
文章日期 | 2024-03-17 | |
操作系统 | Win11 - 22H2 | 22621.2715 |
node -v | v20.10.0 | |
npm -v | 10.2.3 | |
yarn -v | 3.1.1 | |
frida-compile | 10.2.1 | 高版本各种异常 |
扫雷程序下载地址 | https://download.csdn.net/download/kinghzking/88979919 | |
课程源码 | https://gitcode.net/kinghzking/MyOpen | 所在目录:/course/frida |
1️⃣ 分析思路
获取窗口句柄
方案一:FindWindow
最顶层符合条件的窗口,通过SpyXX.exe,我们可以传参扫雷
获取我们想要的结果,但是当有多个窗口时,只能获得第一个。
方案二:内存特征方案
除了FindWindow,我们还可以找到程序内存放该句柄的地址,然后从中取出来。
- 通过SpyXX,我们可以拿到窗口句柄
00000000000A1D86
- 然后,通过CE,我们查找该数值(32位应用,4字节的类型),定位到一个基址
00005B24
最后,我们重启应用,并重复上述步骤,验证几次,确保基址00005B24
可用。
将窗口客户区域重新绘制
InvalidateRect
需要用到两个参数:
- HWND : 固定偏移
00005B24
中取值。- RECT : 需要刷新的区域,窗口的客户区域(也就是除了标题、菜单栏的区域);通过
GetClientRect
函数获取
函数原型如下:
BOOL InvalidateRect(
[in] HWND hWnd,
[in] const RECT * lpRect,
[in] BOOL bErase
);
BOOL GetClientRect(
[in] HWND hWnd,
[out] LPRECT lpRect
);
2️⃣ 代码编码
获取窗口句柄
- 声明变量,类型为
NativePointer
。(也可以是number,因为对于windows应用层,句柄就是一个整数值)- 初始化:这个跟其它变量初始化一样的,只是最后调用的是
readPointer()
方法,返回一个指针对象。
class L07 {
// 声明指针
private hWnd: NativePointer = ptr(0);
constructor() {
// 初始化游戏相关数据
this.hWnd = this.module_winmine.base.add(0x5B24).readPointer();
}
编写API接口
为了方便使用,也为了模块化编程,我们将windows API封装到一个类中,并将该类独立到一个文件中。
- 声明类User32,并默认导出
- 声明静态变量address_GetClientRect,类型为指针。
- 通过Module.findExportByName获取接口
- 根据地址及声明类型,返回NativeFunction对象,用于后期调用本地(C++)函数
以
GetClientRect
为例,代码如下:
// 声明类User32,并默认导出
export default class User32 {
// 声明静态变量address_GetClientRect,类型为指针。
private static address_GetClientRect: NativePointerValue | null;
static GetClientRect(hWnd: NativePointerValue, lpRect: NativePointerValue): number {
// 若address_GetClientRect为null,则通过Module.findExportByName获取接口
if (this.address_GetClientRect == null) {
this.address_GetClientRect = Module.findExportByName("User32.dll", "GetClientRect");
}
// 根据地址及声明类型,返回NativeFunction对象,用于后期调用本地(C++)函数
return new NativeFunction(this.address_GetClientRect!, "bool", ["pointer", "pointer"])(hWnd, lpRect);
}
}
ps: TS 中的感叹号,称作『非空断言』操作符,Non-null assertion operator
中文理解:
x!
将从 x 值对应的类型集合中排除 null 和 undefined 的类型。比如 x 可能是 string | undefind,则 x! 类型缩窄为 string。
将窗口客户区域重新绘制
- 引入User32:
import User32 from '../winapi/user32'
- 创建内存对象
lpRect
:Memory.alloc(4 * 4)
- 获取客户区域:
User32.GetClientRect
- 重新绘制:
User32.InvalidateRect
代码如下:
board_repaint() {
const lpRect = Memory.alloc(4 * 4);
User32.GetClientRect(this.hWnd, lpRect);
User32.InvalidateRect(this.hWnd, lpRect, 1);
}
3️⃣ 测试验证
添加编译和运行脚本
- frida-compile: 编译代码
- frida.exe: 运行测试
配置如下所示:
{
"name": "04_frida_with_typescript",
"main": "src/index.ts",
"scripts": {
"watch07": "frida-compile ./07_让系统重新绘制指定窗口/index.ts -o ./build/07.js -w",
"runx": "D:/Python/Python371/Scripts/frida.exe -n winmine.exe -l ./build/07.js -q"
}
}
执行
npm run runx
运行编译后的脚步./build/07.js
,这时候就直接重绘了窗口了,验证成功。
ps: 文章中内容仅用于技术交流,请勿用于违规违法行为。