坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源: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才会得心应手,不然给我一个代码我都不知道怎么修改,怎么定制我要的功能。