学破解第217天,《攻防世界easy-so》IDA和frida调试回忆

查看 56|回复 10
作者:小菜鸟一枚   
前言:
  坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-2006536-1-1.html
立帖为证!--------记录学习的点点滴滴
0x1 基础准备
  1.先去攻防世界:下载目标easy-so题目的文件https://adworld.xctf.org.cn/media/file/task/456c1dab04b24036ba1d6e32a08dc882.apk
  2.工具:雷电模拟器、NP管理器、IDA、frida、deepseek。
  3.本文目的,每次小菜鸟隔好久再来弄就忘记怎么操作,这个干脆把常用的工具步骤记得详详细细的,温故而知新。
  4.向deepseek提问,从简洁到复杂,我要干什么?-》我在干什么?遇到什么问题?,如果解决方式不是自己想要的,告诉它以XX的方式干这个事情。
  5.注意事项:

1)、雷电模拟器要开启root、硬盘可写入、开发者模式,最好不要有第三方root程序,防止因为未知原因干扰root权限调用
2)、ida和frida调试程序时要上传对应版本的服务端,连接不上模拟器可以通过端口转发,ip的方式进行连接。

  6.逆向思维:

1)、字符串a经过过程1、2、3变成b,现在已知最终b要等于xxx,如果反推a?倒着看从3个步骤开始,如果前面是加,那我推回去就是减,如果前面是第一个字符交换,我推回去就是第二个字符与第一个字符交换,这里会发现这个过程正推逆推都一样,所以可以直接复用这个算法,异或和这个类似,两次异或就还原了。
2)、还有一种情况a经过n多种算法变成b,直接推回去很难,但是字符串比较短,例如只有4位是变化的,那么可以尝试爆破法,复用程序中的算法,穷举的方式比较判断得到输入的值。

0x2 分析java层代码
  1.使用np管理器的安装包提取功能,提取安装包。


1.png (165.55 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传

  2.然后定位到apk文件,点查看


2.png (105.59 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传

  3.打开apk,可以看到有一个输入框,有一个check按钮,随便输入123,点击check按钮提示验证失败。


3.png (65.82 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传

  4.回到np管理器,发起新搜索,搜索“验证失败”字符串


4.png (77.01 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传


1)、定位到如下的smali代码

    .line 28
    :cond_26
    iget-object v2, p0, Lcom/testjava/jack/pingan2/MainActivity$1;->this$0:Lcom/testjava/jack/pingan2/MainActivity;
    const-string v3, "验证失败!"
    invoke-static {v2, v3, v4}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    move-result-object v2
    invoke-virtual {v2}, Landroid/widget/Toast;->show()V

2)、向上翻可以看到关键跳前面的函数CheckString

    .line 22
    .local v1, "strIn":Ljava/lang/String;
    invoke-static {v1}, Lcom/testjava/jack/pingan2/cyberpeace;->CheckString(Ljava/lang/String;)I
    move-result v2
    if-ne v2, v4, :cond_26

3)、再往上翻是onclick函数,也就说我们输入内容,单击check按钮后会进入这个函数,然后通过CheckString校验我们输入的字符串是否正确。

# virtual methods
.method public onClick(Landroid/view/View;)V
  5.选择这个函数,点三个点的地方就能跳转到函数声明处,然后看到了native关键字,说明要去so层才能看到代码。


5.png (163.91 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传

# classes.dex
.class public Lcom/testjava/jack/pingan2/cyberpeace;
.super Ljava/lang/Object;
.source "cyberpeace.java"
# direct methods
.method static constructor ()V
    .registers 1
    .prologue
    .line 9
    const-string v0, "cyberpeace"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
    .line 10
    return-void
.end method
.method public constructor ()V
    .registers 1
    .prologue
    .line 7
    invoke-direct {p0}, Ljava/lang/Object;->()V
    return-void
.end method
.method public static native CheckString(Ljava/lang/String;)I
.end method
  6.点np管理器右上角的三个点可以将smali代码转换为java代码,因为这里java层代码没什么可分析的,所以java层的分析到此为止了。


6.png (184.84 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传

//
// Decompiled by Jadx (from NP Manager)
//
package com.testjava.jack.pingan2;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
class MainActivity$1 implements OnClickListener {
    final /* synthetic */ MainActivity this$0;
    public void onClick(View view) {
        if (cyberpeace.CheckString(((EditText) this.this$0.findViewById(2131165233)).getText().toString()) == 1) {
            Toast.makeText(this.this$0, "验证通过!", 1).show();
        } else {
            Toast.makeText(this.this$0, "验证失败!", 1).show();
        }
    }
    MainActivity$1(MainActivity mainActivity) {
        this.this$0 = mainActivity;
    }
}
0x3 so函数分析
  1.回到np管理器,找到apk文件这次还是点查,进入到lib目录(存放so文件),因为这里用的是雷电模拟器64位,所以选择x86_64位的so文件。ida安装在我们的win系统上,所以选左边文件,长按可以添加到右边,长按右边可以添加到左边,右边的目录实际映射到本机的。


7.png (81.4 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传

  2.使用雷电模拟器自带的文件夹共享功能,打开电脑文件夹就能找到那个so文件了。


8.png (81.97 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传

  3.使用64位 ida加载so文件,找到CheckString函数,按F5就会出现伪代码


9.png (119.09 KB, 下载次数: 0)
下载附件
2025-4-26 16:52 上传

_BOOL8 __fastcall Java_com_testjava_jack_pingan2_cyberpeace_CheckString(__int64 a1, __int64 a2, __int64 a3)
{
  const char *v3; // r14
  size_t v4; // rax
  int v5; // r15d
  unsigned __int64 v6; // r12
  char *v7; // rax
  char *v8; // r13
  bool v9; // cc
  size_t v10; // r12
  size_t v11; // rbx
  char v12; // al
  char v13; // al
  size_t v14; // rbx
  char v15; // al
  v3 = (const char *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0LL);
  v4 = strlen(v3);
  v5 = v4;
  v6 = (__int64)((v4 > 32;
  v7 = (char *)malloc(v6);
  v8 = v7;
  v9 = v6 = 2 )
  {
    v11 = 0LL;
    do
    {
      v12 = v8[v11];
      v8[v11] = v8[v11 + 16];
      v8[v11++ + 16] = v12;
    }
    while ( strlen(v8) >> 1 > v11 );
  }
  v13 = *v8;
  if ( *v8 )
  {
    *v8 = v8[1];
    v8[1] = v13;
    if ( strlen(v8) >= 3 )
    {
      v14 = 2LL;
      do
      {
        v15 = v8[v14];
        v8[v14] = v8[v14 + 1];
        v8[v14 + 1] = v15;
        v14 += 2LL;
      }
      while ( strlen(v8) > v14 );
    }
  }
  return strcmp(v8, "f72c5a36569418a20907b55be5bf95ad") == 0;
}
  4.可以看到大概就是v8经过一系列运算,最后和f72c5a36569418a20907b55be5bf95ad比较,相等就是验证通过,如果是原来我需要一行行阅读代码,现在借助deepseek吧,试了试分析的结果,我拿去输入验证失败。
一、变换流程分析
函数对输入字符串 v3 进行两次变换:
1. 第一阶段变换(位置交换)
操作:交换 v8 和 v8[i+16](0 ≤ i
  5.deepseek的分析看起来很专业,为什么不对呢?先记下deepseek的分析,手动模拟一下变换过程。

1)、将字符串的前16个字符与后16个字符交换位置

f72c5a36569418a20907b55be5bf95ad
f72c5a36569418a2
0907b55be5bf95ad
0907b55be5bf95adf72c5a36569418a2

2)、第二阶段变换:字符对交换,每两个字符交换一次

09
07
b5
5b
e5
bf
95
ad
f7
2c
5a
36
56
94
18
a2
90705bb55efb59da7fc2a5636549812a

3)、最终得到flag:90705bb55efb59da7fc2a5636549812a,拿去验证通过。
4)、这里说明一下,是输入按照这个步骤变换成输出,为什么我输出作输入可以模拟直接这样换,因为第一排和第二排交换,再次一次交换就换回了,第一个字符和第二个字符交换,再次一次也是换回了,算法都一样。

  6.我以为deepseek在手就能通杀简单的CTF,万万没想到ai逻辑分析正确,代码和输出结果不对,但凡我会写代码都不会吃这样的亏,流下来不会算法的泪水,还好算法不复杂,靠大脑就能手动计算出来。


QQ20250426-170921.png (38.58 KB, 下载次数: 0)
下载附件
2025-4-26 17:10 上传

0x4 使用IDA调试so文件
  1.打开cmd窗口,adb devices,adb push android_x64_server /sbin,然后报错了,权限不足,这种问题真烦人,使用批量多开器,新建一个模拟器(为了重置模拟器环境排除干扰),打开root和磁盘可读写功能。


10.png (91.37 KB, 下载次数: 0)
下载附件
2025-4-26 16:53 上传

  2.然后问问deepseek怎么解决。
adb root          # 获取 Root 权限
adb remount       # 自动重新挂载 /system 为可写
adb push android_x64_server /system/bin/
adb shell chmod +x /system/bin/android_x64_server
实际执行结果
C:\MySofeware\IDA_Pro_7.7\dbgsrv>adb root
restarting adbd as root
C:\MySofeware\IDA_Pro_7.7\dbgsrv>adb remount
remount succeeded
C:\MySofeware\IDA_Pro_7.7\dbgsrv>adb push android_x64_server /system/bin/
android_x64_server: 1 file pushed, 0 s...d. 54.8 MB/s (1230968 bytes in 0.021s)
C:\MySofeware\IDA_Pro_7.7\dbgsrv>adb shell chmod +x /system/bin/android_x64_server
  3.启动android server,然后输入adb forward tcp:23946 tcp:23946命令,将手机上的23946窗口,转发到我们电脑本地的23946端口,adb shell am start -n com.testjava.jack.pingan2/.MainActivity。
adb shell /system/bin/android_x64_server &  //& 表示后台运行。
C:\MySofeware\IDA_Pro_7.7\dbgsrv>adb shell /system/bin/android_x64_server &
IDA Android x86 64-bit remote debug server(ST) v7.7.27. Hex-Rays (c) 2004-2022
Listening on 0.0.0.0:23946...
C:\Users\LENOVO\Desktop>adb shell am start -n com.testjava.jack.pingan2/.MainActivity
Starting: Intent { cmp=com.testjava.jack.pingan2/.MainActivity }
  4.使用F2在IDA中下上断点,颜色会变红,然后点击菜单栏debugger->Attach to process...,找到对应报名就成功附加调试了。


11.png (78.21 KB, 下载次数: 0)
下载附件
2025-4-26 16:53 上传

  5.F9运行,到app输入f72c5a36569418a20907b55be5bf95ad, 开始单步调试走,重点观察v8的变换过程。

1)、首先看第一个循环代码

  memcpy(v8, v3, v5);//这一行v8开始将我输入的值复制过去了
      v12 = v8[v11];
      v8[v11] = v8[v11 + 16];
      v8[v11++ + 16] = v12;
do
    {
      v12 = v8[v11];
      v8[v11] = v8[v11 + 16];
      v8[v11++ + 16] = v12;
    }
    while ( strlen(v8) >> 1 > v11 );
        //一轮循环
          f72c5a36569418a20907b55be5bf95ad
          072c5a36569418a2f907b55be5bf95ad
        //二轮循环
          092c5a36569418a2f707b55be5bf95ad
        //最后一轮交换后
          0907b55be5bf95adf72c5a36569418a2
        //经过反复调试,可以看出这里就是交换前16位和后16位的字符,逐一交换

2)、在继续往下走

  v13 = *v8;
    *v8 = v8[1];
    v8[1] = v13;
        //这里可以看出是将第一个字符和第二个字符交换,if语句都是干扰
        v14 = 2LL;
      do
      {
        v15 = v8[v14];
        v8[v14] = v8[v14 + 1];
        v8[v14 + 1] = v15;
        v14 += 2LL;
      }
      while ( strlen(v8) > v14 );
        //这里下标从2开始就是从第三个字符开始交换,+1可以看出是第三个字符和后一个字符交换,重复这个过程
        //结束循环时v8的值为:90705bb55efb59da7fc2a5636549812a
        return strcmp(v8, "f72c5a36569418a20907b55be5bf95ad") == 0;
        //最后这里就是判断是否相等了。

3)、这里说明一下,IDA调试命令和OD差不多,F2下断点,F8单步运行,F4运行到鼠标指定的行,鼠标放在v8上面就可以看到具体内容了。



12.png (153.25 KB, 下载次数: 0)
下载附件
2025-4-26 16:53 上传

  6.告诉deepseek:使用java编写一段代码,计算“f72c5a36569418a20907b55be5bf95ad”每两位交换一次,最后将前16位与后16位交换后的值。
package test;
public class Test {
    public static void main(String[] args) {
        String original = "f72c5a36569418a20907b55be5bf95ad";
        // 第一步:每两位交换一次
        String swapped = swapEveryTwoChars(original);
        System.out.println("每两位交换后: " + swapped);
        // 第二步:交换前16位和后16位
        String result = swapFirstAndSecondHalf(swapped);
        System.out.println("最终结果: " + result);
    }
    // 每两位交换一次
    public static String swapEveryTwoChars(String str) {
        char[] chars = str.toCharArray();
        for (int i = 0; i
  7.我算是发现了,deepseek给的分析过程合情合理,代码也比较准确,但是给不了你正确的值,需要你自己动手跑一遍,deepseek会惩罚不想动手的人。


13.png (48.89 KB, 下载次数: 0)
下载附件
2025-4-26 16:53 上传

0x5 使用frida hook so中的函数
  1.先看一下我系统里面frida的版本,然后下载对应版本的服务端去https://github.com/frida/frida/releases/tag/16.6.6:frida-server-16.6.6-android-x86_64.xz
C:\Users\LENOVO>frida --version
16.6.6
  2.将下载sever解压,重命名为frida-server(只是为了名称短点,改成别的都行),传到模拟器上运行,提示权限不足就加上su命令。
adb push frida-server /data/local/tmp/
adb shell chmod +x /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &
  3.我告诉deepseek你给我hook v8的返回值,一直提示找不到Java_com_testjava_jack_pingan2_cyberpeace_CheckString函数,自定义函数不能hook吗?最后选择去hook strcmp这个库函数。
Java.perform(function() {
    // 1. 查找strcmp函数
    const strcmp = Module.findExportByName(null, "strcmp");
    if (!strcmp) {
        console.log("[-] 找不到strcmp函数");
        return;
    }
    console.log("[+] strcmp函数地址: " + strcmp);
    // 2. Hook strcmp函数
    Interceptor.attach(strcmp, {
        onEnter: function(args) {
            // 读取两个比较字符串
            const str1 = args[0].readUtf8String();
            const str2 = args[1].readUtf8String();
            // 检查是否是我们要找的比较(与目标字符串比较)
            const targetStr = "f72c5a36569418a20907b55be5bf95ad";
            if (str2 === targetStr) {
                console.log("\n[+] 捕获到v8字符串比较:");
                console.log("变换后的字符串(v8):", str1);
                console.log("目标字符串:", str2);
                // 保存到全局变量以便在onLeave中使用
                this.isTargetComparison = true;
                this.v8str = str1;
            }
        },
        onLeave: function(retval) {
            if (this.isTargetComparison) {
                console.log("比较结果:", retval.toInt32() === 0 ? "匹配" : "不匹配");
                console.log("完整的v8字符串内容:", this.v8str);
                // 如果你想强制返回匹配结果,可以取消下面注释
                // retval.replace(0); // 0表示匹配
            }
        }
    });
    console.log("[+] strcmp hook已安装,等待捕获v8字符串...");
});
  4.这里直接 -U一直显示等待连接,然后还是选择老办法adb转发端口,然后使用ip地址连接模拟器。
C:\Users\LENOVO\Desktop>frida -H 127.0.0.1:27042 -f com.testjava.jack.pingan2 -l
hook.js
     ____
    / _  |   Frida 16.6.6 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to 127.0.0.1:27042 ([email protected]:27042)
Spawned `com.testjava.jack.pingan2`. Resuming main thread!
[Remote::com.testjava.jack.pingan2 ]-> [+] strcmp函数地址: 0x7ffff6726020
[+] strcmp hook已安装,等待捕获v8字符串...
[+] 捕获到v8字符串比较:
变换后的字符串(v8): 90705bb55efb59da7fc2a5636549812a
目标字符串: f72c5a36569418a20907b55be5bf95ad
比较结果: 不匹配
完整的v8字符串内容: 90705bb55efb59da7fc2a5636549812a
  5.函数是静态注册的,并且从IDA中可以到已导出,为什么不能hook呢,deepseek说可能延迟加载了,所以我得这样问deepseek:使用Frida Hook监控V8值变化(处理SO延迟加载,函数已导出)


14.png (67.34 KB, 下载次数: 0)
下载附件
2025-4-26 16:53 上传

  6.最终得到如下脚本,其中目标SO文件名、目标导出函数名要根据实际情况修改(so文件名就是apk解压出来的那个,不要改动,目标函数函数要写全,IDA里面一般识别到的java开头的那个就是)。
Java.perform(function() {
    // 配置参数
    const config = {
        targetSo: "libcyberpeace.so",      // 目标SO文件名
        targetFunction: "Java_com_testjava_jack_pingan2_cyberpeace_CheckString",     // 目标导出函数名
        targetString: "f72c5a36569418a20907b55be5bf95ad", // 用于识别比较的目标字符串
        maxRetries: 10,                    // 最大重试次数
        retryInterval: 1000                // 重试间隔(ms)
    };
    console.log(`[+] 开始Hook目标函数: ${config.targetSo}!${config.targetFunction}`);
    // 1. 等待SO加载并获取函数地址
    let retryCount = 0;
    let targetFuncAddress = null;
    function waitAndHook() {
        const mod = Process.findModuleByName(config.targetSo);
        if (mod) {
            console.log(`[+] 模块已加载: ${config.targetSo} @ ${mod.base}`);
            // 获取导出函数地址
            targetFuncAddress = Module.getExportByName(config.targetSo, config.targetFunction);
            if (targetFuncAddress) {
                console.log(`[+] 找到导出函数 ${config.targetFunction} @ ${targetFuncAddress}`);
                setupHooks();
            } else {
                console.log(`[-] 在${config.targetSo}中未找到导出函数${config.targetFunction}`);
            }
        } else if (retryCount
  7.这里so文件加载好像必须是我点check按钮后才会加载。
0x6 总结
  还是得有点基础,用起deepseek才会得心应手,不然给我一个代码我都不知道怎么修改,怎么定制我要的功能。

函数, 字符串

yan999   

感谢楼主!!!学习了
longcy   

太厉害了 感谢楼主的分享~!~!是我的话到deepseek那儿就把我难住了 因为我不会去看代码计算的是不是正确的哈哈
84huanyu   

  1.使用np管理器的安装包提取功能,提取安装包。
wzjzdqh   

学习了感谢分享
isbeichen   

感谢分享
weirdo   

来学习学习~ 感谢楼主分享~ 好多办法啊~
8486361321   

楼主方法真多,感谢分享,学习一下。
Lalala98   

这个可以,我之前反编译后不知道还能这么操作,我就只会改改跳转,返回值,值得学习
hyhj2000   

感谢分享啊!!!
您需要登录后才可以回帖 登录 | 立即注册