首页
论坛
课程
招聘
[原创]HoneyBadger实战:虚拟化在调试常规so中的使用及插件开发教程
2023-1-22 11:50 15332

[原创]HoneyBadger实战:虚拟化在调试常规so中的使用及插件开发教程

2023-1-22 11:50
15332

概述

本文演示如何调试常规的so,及调试插件开发。
插件功能包含:

  • 调用函数
  • 设置参数、打印函数返回值
  • 设置机型等信息

大家感兴趣的方面也可以私信我,可以附加样本(不要是国内的具体产品,除非是名声在外口碑很好的,技术不行法务来凑的那种一律不分析),我优先写相关的文章。

 

正文开始。

一、编写测试so

我使用的IDE: Android Studio Electric Eel | 2022.1.1
Android Studio版本不重要,随便什么版本都行,环境配置好后应当都能编译通过。

 

源码我已经上传,地址: https://github.com/honeybadger8mg/honeybadger/tree/master/examples/AndroidDemo

 

我们要测试的是nativelib1这个module编译出来的动态库。

 

native函数声明是

1
2
3
4
5
6
7
8
9
10
11
package com.honeybadger.nativelib;
 
public class NativeLib1 {
    static {
        System.loadLibrary("nativelib1");
    }
 
    public static native String DoTest1(String strArg);
 
    public static native int DoTest2(int iArg);
}

我们演示的是 DoTest1和DoTest2 两个函数,其代码实现是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <jni.h>
#include <string>
 
extern "C" JNIEXPORT jstring JNICALL
Java_com_honeybadger_nativelib_NativeLib1_DoTest1(
        JNIEnv* env,
        jclass, jstring jsArg) {
    const char *szData = env->GetStringUTFChars(jsArg, JNI_FALSE);
    std::string strData = szData;
    env->ReleaseStringUTFChars(jsArg, szData);
 
    std::string strOut = strData + " 已处理";
    return env->NewStringUTF(strOut.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_honeybadger_nativelib_NativeLib1_DoTest2(JNIEnv *env, jclass clazz, jint i_arg) {
    int iOut = i_arg + 100;
    return iOut;
}

编译过程直接导入即可,略。

二、编写插件dll

源码我已经上传,地址:
https://github.com/honeybadger8mg/honeybadger/tree/master/examples/pluginDemo

 

IDE:CLion 2022.3.1 这个建议大家就用最新版吧
工具链:

  • MinGW-w64
    我这里版本是 11.0,版本应该不太重要,不是很老应该问题不大
  • CMake
    Clion会自动选择为自带的,也可以自己设置,不重要,我这里显示的版本是 3.24.2
  • Build Tool:
    Clion会自动选择,ninja.exe
  • C Compile:
    Clion会自动选择,gcc.exe或者cc.exe
  • C++ Compile:
    Clion会自动选择,c++.exe

注意:编译类型为release,因为要依赖我的dll,debug版本太大了,无法传git,所以编译类型为debug时可能会失败

 

如图:

源码结构

  • hb_headers、hb_libs
    这两个目录为插件开发可能需要的头文件和动态库,其包含了常见Android的Java类及工具类等
    不需要改动
  • include
    • IRestrictedListener.h
      为了便于记忆放到这里,不需要改动
    • PluginCommHeader.h
      菜单的结构体定义,不需要改动
    • PluginExportsDefines.h
      插件的导出函数定义,不需要改动
  • src
    插件的源码,这里就随意编写了
    • BizJavaCls
      这里是Java类的编写,大家根据自己的编码习惯处理即可,我习惯的是单独放到一起
    • CBizWorker
      目前没有用到,可以不关心,调试器用不到。
      其主要用于并发的场景,比如同时虚拟执行多个so,或者同一个so同时跑多个,在HoneyBadger的SDK场景中比较常见。
    • PluginExport
      插件的功能代码,插件主要在这里编写
插件开发步骤

大家也可以直接clone代码改写。
插件一共4个必须的导出函数,如下:

 

然后分别实现即可。

  • GetMenu 插件菜单
    即调试器右键后插件菜单中的子菜单,实现逻辑是:
    HoneyBadger调用插件的GetMenu函数获得菜单双向链表HBMenuChain得到name和id,然后展示name。
    当执行时,将菜单的id传给插件的DispatchAction进行分发执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    HBMenuChain *GetMenu() {
      if (g_pMenu == nullptr) {
          g_pMenu = CreateMenu("调用DoTest1", 1, nullptr);
          HBMenuChain *pSecond = CreateMenu("调用DoTest2", 2, g_pMenu);
          g_pMenu->next = pSecond;
     
          HBMenuChain *pThird = CreateMenu("其他函数", 3, pSecond);
          pSecond->next = pThird;
      }
      return g_pMenu;
    }
  • SetRestrictedListener
    一般不需要处理,保持原状即可
  • DoPrepare
    如果你想调试时把设备信息等也改掉,或者样本中检测了包名等的情况,那就在这里处理,此处仅是示例,全部都能改,如果不需要就不用管

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void DoPrepare() {
      if (g_pListener == nullptr) {
          CTLOG("没有设置 IRestrictedListener");
          return;
      }
     
      g_pListener->SetAndroidId(IceRandom::NextString16(16));
      g_pListener->SetPackageName("com.honeybadger.androiddemo");
      g_pListener->SetLableName("测试");
      g_pListener->SetFinger("xiaomi/onc/onc:9/PKQ1.181021.001/V10.3.2.0.PFLCNXM:user/release-keys");
    //    g_pListener->SetApkPath("xxxxxx.apk"); 可设可不设   主程序会自动设置
      g_pListener->SetSignatureHashCode(0x1122334);
      g_pListener->SetSignatureBuf(CPublicUtil::Hex2Str(
              "30820356308202太长了影响排版,明白意思即可"));
    }
  • DispatchAction
    菜单的响应分发

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    void DispatchAction(int iActId) {
      switch (iActId) {
          case 1: {
              // 调用 DoTest1
              CTLOG("准备执行 DoTest1...");
              java_lang_String *pJsArg = new java_lang_String(g_pListener->GetEntry());
              pJsArg->SetContent("hello world");
     
              com_honeybadger_nativelib_NativeLib1 *pThis = new com_honeybadger_nativelib_NativeLib1(g_pListener->GetEntry());
              vector<uint32_t> vecPara = {g_pListener->GetJniEnvAddr(), pThis->GetInstanceID(), pJsArg->GetInstanceID()};
              map<uint32_t, uint64_t> mapResult;
              g_pListener->ExecJniInThread(true, "Java_com_honeybadger_nativelib_NativeLib1_DoTest1", 3, vecPara,
                                           &mapResult);
     
              uint32_t iOut = mapResult[ARM_REG_R0];
              java_lang_String *pJsOut = (java_lang_String *) g_pListener->GetEntry()->GetVaClassInst()->SearchClass_Inst(iOut);
              string strOut = "执行 DoTest1 结束,结果:" + pJsOut->GetContent();
              CTLOG(strOut.c_str());
              break;
          }
          case 2: {
              CTLOG("准备执行 DoTest2...");
              uint32_t iArg = 100;
     
              com_honeybadger_nativelib_NativeLib1 *pThis = new com_honeybadger_nativelib_NativeLib1(g_pListener->GetEntry());
              vector<uint32_t> vecPara = {g_pListener->GetJniEnvAddr(), pThis->GetInstanceID(), iArg};
              map<uint32_t, uint64_t> mapResult;
              g_pListener->ExecJniInThread(true, "Java_com_honeybadger_nativelib_NativeLib1_DoTest2", 3, vecPara,
                                           &mapResult);
     
              uint32_t iOut = mapResult[ARM_REG_R0];
              string strOut = "执行 DoTest2 结束,结果:" + CPublicUtil::Int2String(iOut);
              CTLOG(strOut.c_str());
              break;
          }
          case 3:{
              CTLOG("这里是继续添加其他函数的处理过程");
              break;
          }
          default: {
              std::cout << "没有实现的函数" << std::flush;
              break;
          }
      }
    }

    编译即可

调试

  • 一、配置

在实战中,动态库目录一般是直接解压的lib目录,此处因为工程是我们自己的,所以我选择的中间目录merged_native_libs,省去反复解压的过程

 

插件选择好后,可以点击下“测试插件”按钮,正常的情况下日志将会如上图显示,熟悉了就不用点击测试了。

 

配置设置完成后,点击 “启动调试” 按钮,调试器将会自动执行。

  • 二、自动执行部分
    此时可切到日志页面(可能界面会卡一会,界面还需要处理下),或者直接看日志文件。
    看到日志"xxxxxx.so 初始执行完成"字样后即可调用测试函数(即Jni_Onload系列函数执行完成),如图

  • 三、调用测试函数 DoTest1 和 DoTest2
    直接右键

    然后切到日志页面,可看到日志

    可以看到,目的达到,结束。

结束

本文介绍调试和插件编写的主要过程,大家可以直接看代码,来的直接。
发现Bug请github提issue,有疑问可与我联系。
谢谢大家。


[2023春季班]《安卓高级研修班(网课)》月薪两万班招生中~

最后于 2023-1-22 15:05 被eightmg编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 153
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
万里星河 活跃值 2023-1-24 23:20
2
0
mark
雪    币: 23
活跃值: 活跃值 (169)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
大枫叶 活跃值 2023-1-26 11:54
3
0
mark
游客
登录 | 注册 方可回帖
返回