🛫 系列文章导航
- 【Frida】 00_简单介绍和使用 https://blog.youkuaiyun.com/kinghzking/article/details/123225580
- 【Frida】 01_食用指南 https://blog.youkuaiyun.com/kinghzking/article/details/126849567
- 【Frida】02_常见API示例及功能函数封装(snippets)https://blog.youkuaiyun.com/kinghzking/article/details/136903974
- 【Frida】 03_初识frida-node https://blog.youkuaiyun.com/kinghzking/article/details/136685316
- 【Frida】 04_Frida中使用TypeScript脚本(采坑) https://blog.youkuaiyun.com/kinghzking/article/details/136772475
- 【Frida】 05_读取扫雷游戏的数据 https://blog.youkuaiyun.com/kinghzking/article/details/136781623
- 【Frida】 06_分析扫雷游戏的数据,显示地雷位置 https://blog.youkuaiyun.com/kinghzking/article/details/136685316
- 【Frida】 07_让系统重新绘制指定窗口 https://blog.youkuaiyun.com/kinghzking/article/details/136829854
- 【Frida】 08_将目标窗口切换到前台 https://blog.youkuaiyun.com/kinghzking/article/details/136837275
- 【Frida】 09_获取软件窗口位置,设置鼠标指针位置 https://blog.youkuaiyun.com/kinghzking/article/details/136854052
- 【Frida】10_用鼠标自动标记棋盘上的雷区(一键过关) https://blog.youkuaiyun.com/kinghzking/article/details/136854020
- 【frida-实战】“一行”代码教你获取WeGame平台中所有的lua脚本 https://blog.youkuaiyun.com/kinghzking/article/details/125590584
- 【Frida-实战】EA游戏平台的文件监控(PsExec.exe提权) https://blog.youkuaiyun.com/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.youkuaiyun.com/download/kinghzking/88979919 | |
课程源码 | https://gitcode.net/kinghzking/MyOpen | 所在目录:/course/frida |
1️⃣ Frida API 介绍
日志函数
为了方便查看测试结果,编写了两个日志函数
logH1
和logH2
。
示例代码:
function logH1(title: String) {
console.log(`
=================================================================
================================ ${title}
=================================================================
`)
}
function logH2(title: String) {
console.log(`
================================ ${title}
`)
}
export { logH1, logH2 } // 导出函数
运行时信息:Frida、Script
本小节讲述几个简单的属性:可以查看Frida版本、堆大小、运行时类型(QJS or V8)等信息。
示例代码:
function getRuntimeInfo() {
logH1("Runtime information")
// Frida
logH2("Frida")
console.log('Frida.version:', Frida.version)
console.log('Frida.heapSize:', Frida.heapSize)
// Script (QJS or V8)
logH2("Script")
console.log('Script.runtime:', Script.runtime)
}
运行结果:
frida.exe可以通过参数--runtime=v8
指定runtime
类型。
进程(Process)
进程相关操作,部分函数(如getCurrentDir)注意版本:frida 15.0.18 还不支持;16.0.8 版本测试通过。
示例代码:
function show_process() {
logH1("Process")
console.log("Process.id:\t\t", Process.id);
console.log("Process.getCurrentThreadId():\t", Process.getCurrentThreadId());
console.log("Process.arch:\t\t", Process.arch);
console.log("Process.platform:\t", Process.platform);
console.log("Process.pageSize:\t", Process.pageSize);
console.log("Process.pointerSize:\t", Process.pointerSize);
console.log("Process.codeSigningPolicy:\t", Process.codeSigningPolicy);
// 下面三个接口, frida 15.0.18 还不支持;16.0.8 版本测试通过
console.log("Process.getCurrentDir():\t", Process.getCurrentDir());
console.log("Process.getHomeDir():\t", Process.getHomeDir());
console.log("Process.getTmpDir():\t", Process.getTmpDir());
logH2("Process.enumerateThreads")
let threads = Process.enumerateThreads();
for (const iterator of threads) {
console.log(JSON.stringify(iterator));
}
logH2("Process.enumerateModules")
let modules = Process.enumerateModules();
for (const iterator of modules) {
console.log(JSON.stringify(iterator));
}
logH2("Process.enumerateRanges")
let ranges = Process.enumerateRanges("rwx");
for (const iterator of ranges) {
console.log(JSON.stringify(iterator));
}
// let mallocRanges = Process.enumerateMallocRanges();
// for (const iterator of mallocRanges) {
// console.log(JSON.stringify(iterator));
// }
}
运行结果:
Thread(线程)
Thread相关接口就两个,一个是打印堆栈的
Thread.backtrac
;另一个就是Thread.sleep
,需要注意的是单位为秒,而不是毫秒。
示例代码:
// Thread
function show_thread() {
logH1("Thread")
// Thread.backtrace 有机会以后再写吧
// Thread.sleep
console.log("Thread.sleep(1000) start...");
// 单位:秒、seconds
Thread.sleep(1)
console.log("Thread.sleep(1000) finish...");
}
运行结果:
Module(模块)
Module在Windows就是PE文件(EXE、DLL等),frida可以对PE进行解析,获取各种有用的信息:模块基址、名称、导入、导出、符号、内存区等信息。
示例代码:
function show_module() {
logH1("Module")
let module = Process.getModuleByName("winmine.exe");
// let module = Process.getModuleByName("user32.dll");
// module = Process.getModuleByName("Kernel32.dll");
console.log("module", JSON.stringify(module, null, 4));
logH2("Imports:");
for (const iterator of module.enumerateImports()) {
console.log(JSON.stringify(iterator));
}
logH2("Exports:");
for (const iterator of module.enumerateExports()) {
console.log(JSON.stringify(iterator));
}
logH2("Symbols:");
for (const iterator of module.enumerateSymbols()) {
console.log(JSON.stringify(iterator));
}
// enumerateRanges
logH2("Ranges:");
for (const iterator of module.enumerateRanges("r--")) {
console.log(JSON.stringify(iterator));
}
// {"type":"function","name":"lstrlenW","address":"0x7630e0b0"}
let p = module.findExportByName("lstrlenW");
console.log(p);
let p1 = Module.load("DBGHELP.DLL");
console.log(JSON.stringify(p1));
logH2("Exports:");
for (const iterator of p1.enumerateExports()) {
console.log(JSON.stringify(iterator));
}
logH2("Imports:");
for (const iterator of p1.enumerateImports()) {
console.log(JSON.stringify(iterator));
}
}
运行结果:
Memory(内存)
内存操作是Frida核心内容,包含下面几方面:
- 分配内存:
Memory.alloc、Memory.allocUtf8String
等。- 特征码扫描:
Memory.scan、Memory.scanSync
。- 修改内存区域保护状态:
Memory.protect
- 修改代码:
Memory.patchCode
- 其它各种操作,可以通过
NativePointer
操作。
示例代码:
// Memory.scan(module.base, module.size, pattern, {
Memory.scan(module.base, module.size, "04 ?? ?1 ?0", {
onMatch: (address, size) => {
console.log("onMatch", size, address, address.sub(module.base));
},
onError: (reason) => {
console.log(reason);
},
onComplete: () => {
console.log("Scan Complete!");
}
});
// let matches = Memory.scanSync(module.base, module.size, pattern);
let matches = Memory.scanSync(module.base, module.size, "04 ?? ?1 ?0");
for (const iterator of matches) {
console.log(JSON.stringify(iterator));
}
let m1 = Memory.alloc(Process.pageSize);
console.log("protect", JSON.stringify(Process.getRangeByAddress(m1)));
Memory.protect(m1, Process.pageSize, "r-x");
console.log("protect", JSON.stringify(Process.getRangeByAddress(m1)));
let lpText = Memory.allocUtf16String("This is a string!");
let lpCaption = Memory.allocUtf16String("Caption");
// WinApi.MessageBox(p, lpText, lpCaption, 0x00000001);
let m2 = Memory.alloc(Process.pageSize);
console.log("m2", m2);
let address = Module.getExportByName("User32.dll", "MessageBoxW");
Memory.patchCode(m2, Process.pageSize, (code) => {
// console.log("code", code);
let asm = new X86Writer(code);
asm.putPushU32(0x00000001);
asm.putPushU32(lpCaption.toUInt32());
asm.putPushU32(lpText.toUInt32());
// asm.putPushU32(p.toUInt32());
asm.putPushU32(0);
asm.putCallAddress(address);
asm.putRet();
asm.flush();
});
Interceptor(拦截器)
拦截器Interceptor用于在运行时拦截和修改函数调用。通过Frida Interceptor,你可以监视并修改应用程序中特定函数的行为,比如修改函数的参数、返回值等。这对于逆向工程、安全分析和应用程序调试都非常有用。
下面通过拦截
DispatchMessageW
函数演示Windows消息拦截,示例代码:
function show_interceptor() {
// DispatchMessageW
let address = Module.getExportByName("User32.dll", "DispatchMessageW");
// console.log(JSON.stringify(Interceptor));
Interceptor.attach(address, {
onEnter(this, args) {
// console.log(this.context, this.depth, this.errno, this.lastError, this.returnAddress, this.threadId);
console.log(JSON.stringify(this.context));
// typedef struct tagMSG {
// HWND hwnd;
// UINT message;
// WPARAM wParam;
// LPARAM lParam;
// DWORD time;
// POINT pt;
// DWORD lPrivate;
// } MSG, *PMSG, *NPMSG, *LPMSG;
console.log('args[0]: ', args[0]);
console.log(args[1]);
console.log(args[2]);
console.log(args[3]);
console.log(args[4]);
console.log('args[5]: ', args[5]);
let msg = args[0];
console.log("hwnd", msg.readPointer());
console.log("message", msg.add(4).readPointer());
console.log("wParam", msg.add(8).readPointer());
console.log("lParam", msg.add(12).readPointer());
console.log("pt", msg.add(20).readPointer());
console.log("lPrivate", msg.add(24).readPointer());
},
onLeave(this, retval) {
console.log(JSON.stringify(this.context));
console.log(retval);
},
});
}
运行结果:
2️⃣ 功能函数封装(snippets)
下面记录经常使用的函数或者代码片段
显示汇编代码
通过
Instruction
类逐字节读取并解析汇编。
示例代码:
function show_asm(start: NativePointer, length: number = 10) {
for (let index = 0; index < length; index++) {
let inst = Instruction.parse(start);
// console.log(JSON.stringify(inst));
let byteArray = start.readByteArray(inst.size);
let byteCode = Array.prototype.slice.call(new Uint8Array(byteArray!));
let mCode = byteCode.map(x => x.toString(16).padStart(2, "0")).join(" ").toUpperCase();
console.log(inst.address.toString().toUpperCase().replace("0X", "0x"), mCode.padEnd(14, " "), "\t", inst.toString().toUpperCase().replace("0X", "0x"));
start = inst.next;
if (start.readU32() == 0) break;
}
}
function testSnippets() {
let address = Module.getExportByName("User32.dll", "MessageBoxW");
show_asm(address);
}
运行结果:
常见类型转换函数
逆向中经常遇到类型转换,这里列出小编经常使用的一些函数。
// Converts an integer (unicode value) to a char
function itoa(i: number)
{
return String.fromCharCode(i);
}
// Converts a char into to an integer (unicode value)
function atoi(a: { charCodeAt: () => any; })
{
return a.charCodeAt();
}
// i to hex
function itohex(i: { toString: (arg0: number) => any; })
{
var s = i.toString(16);
s = ('00000000' + s).slice(-8);
return [s.slice(6, 8), s.slice(4, 6), s.slice(2, 4), s.slice(0, 2)].join(' ');
}
// itohex(0x1122334);
function str2hex(s: string | any[]) { // buffer is an ArrayBuffer
// for each element, we want to get its two-digit hexadecimal representation
const hexParts = [];
for(let i = 0; i < s.length; i++) {
// convert value to hexadecimal
const hex = atoi(s[i]).toString(16);
// pad with zeros to length 2
const paddedHex = ('00' + hex).slice(-2);
// push to array
hexParts.push(paddedHex);
}
// join all the hex values of the elements into a single string
return hexParts.join(' ');
}
// str2hex('ssssk')
function buf2hex(buffer: Iterable<number>) { // buffer is an ArrayBuffer
// create a byte array (Uint8Array) that we can use to read the array buffer
const byteArray = new Uint8Array(buffer);
// for each element, we want to get its two-digit hexadecimal representation
const hexParts = [];
for(let i = 0; i < byteArray.length; i++) {
// convert value to hexadecimal
const hex = byteArray[i].toString(16);
// pad with zeros to length 2
const paddedHex = ('00' + hex).slice(-2);
// push to array
hexParts.push(paddedHex);
}
// join all the hex values of the elements into a single string
return hexParts.join(' ');
}
虚表操作
虚表作为C++中重要的特征之一,具有举足轻重的作用,通过虚表,我们可以准确定位一些类对象的方法及函数名称等,下面记录小编用到的两个函数:
GetVTableByClassName
和GetVTableNameByObj
。
// 根据类名获取虚表地址
function GetVTableByClassName(dll_name: string, func: string) {
var module = Module.load(dll_name);
var s = ".?AV" + func + "@@";
var results = Memory.scanSync(module.base, module.size, str2hex(s));
// class GxxLogin::LoginAccountPage `RTTI Type Descriptor'
// var nRTTITypeDescriptor = eval(results[0].address)-8;
var nRTTITypeDescriptor = results[0].address-0x08;
// MyLogD(results[0].address, itohex(nRTTITypeDescriptor));
results = Memory.scanSync(module.base, module.size, itohex(nRTTITypeDescriptor));
// MyLogD(JSON.stringify(results));
// RTTI Complete Object Locator
var nRTTI_COL = eval(results[0].address) - 0x0c;
// MyLogD(nRTTI_COL, itohex(nRTTI_COL));
results = Memory.scanSync(module.base, module.size, itohex(nRTTI_COL));
// MyLogD(JSON.stringify(results), eval(results[0].address), eval(results[0].address)-0+0x04);
return eval(results[0].address)-0+0x04;
}
// MyLogD(GetVTable('LoginAccountPage@GxxLogin'));
// 通过对象获取虚表
// .?AVContextualErrorEdit@Phoenix@@ 某网的 accountNameEdit 、 passwordEdit 都是这个类实现的
// .data:1164F34C ; public class Phoenix::ContextualErrorEdit /* mdisp:0 */ :
// .data:1164F34C ; public class QWidget /* mdisp:0 */ :
// .data:1164F34C ; public class QObject /* mdisp:0 */,
// .data:1164F34C ; public class QPaintDevice /* mdisp:8 */
// .data:1164F34C ; class Phoenix::ContextualErrorEdit `RTTI Type Descriptor'
// .data:1164F34C ??_R0?AVContextualErrorEdit@Phoenix@@@8 dd offset ??_7type_info@@6B@
function GetVTableNameByObj(obj: string | number | NativePointer) {
// TODO:
var pCol = ptr(obj).readPointer().sub(4).readPointer();
var pTypeDescriptor = pCol.add(4*3).readPointer();
return pTypeDescriptor.add(4*2).readCString()
}
ps: 文章中内容仅用于技术交流,请勿用于违规违法行为。