【Frida】02_常见API示例及功能函数封装(snippets)

🛫 系列文章导航

🛫 导读

开发环境

版本号描述
文章日期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️⃣ Frida API 介绍

日志函数

为了方便查看测试结果,编写了两个日志函数logH1logH2
示例代码:


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++中重要的特征之一,具有举足轻重的作用,通过虚表,我们可以准确定位一些类对象的方法及函数名称等,下面记录小编用到的两个函数:GetVTableByClassNameGetVTableNameByObj


// 根据类名获取虚表地址
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: 文章中内容仅用于技术交流,请勿用于违规违法行为。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

夜猫逐梦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值