前言:
坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:https://www.52pojie.cn/thread-2006536-1-1.html
立帖为证!--------记录学习的点点滴滴
0x1 是时候学习一下Frida一把梭了(上)
1.到这里是因为动调插桩失败了,学一学这个打印功能,运行程序,输入123点验证,出现密码错误哦。
2.先定位到错误提示这里,定位到关键跳上面的check函数。
1)、提取安装包
2)、打开dex文件,搜索字符串:密钥错误哦,再想想!
3)、定位到onCreate函数,向上翻
invoke-virtual {p0, p1}, Lcom/zj/wuaipojie/ui/ChallengeFourth;->check(Ljava/lang/String;)Z
move-result p1
3.再来分析一下check函数
1)、str这个是我输入的字符串,首先执行了一个startsWith和endsWith函数,百度一下可知判断字符串开头和结尾是否以指定的开头。
2、substring是截取flag{中间的一串的字符串}
3、然后用bytes进行base64再和我们str中间的那一串字符串比较。
4、得到base64Utils.encodeToString(bytes)就能知道str中间的那一部分了。
public final boolean check(String str) {
int i = 0;
Object obj = null;
if (!StringsKt.startsWith$default(str, "flag{", false, 2, null) || !StringsKt.endsWith$default(str, "}", false, 2, null)) {
return false;
}
str = str.substring(5, str.length() - 1);
Intrinsics.checkNotNullExpressionValue(str, "this as java.lang.String…ing(startIndex, endIndex)");
String string = SPUtils.INSTANCE.getString((Context) this, "id", "");
if (string != null) {
obj = Integer.valueOf(string.length());
}
int i2 = 1000;
Intrinsics.checkNotNull(obj);
int intValue = obj.intValue();
if (intValue >= 0) {
while (true) {
i2 -= 7;
if (i == intValue) {
break;
}
i++;
}
}
string = Encode.encode(string + i2);
Base64Utils base64Utils = Base64Utils.INSTANCE;
byte[] bytes = string.getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
return Intrinsics.areEqual(str, base64Utils.encodeToString(bytes));
}
4.进去encodeToString之后看看,我们要的就是p1的返回值。
|
method public final encodeToString([B)Ljava/lang/String;
.registers 3
const/4 v0, 0x2
.line 11
invoke-static {p1, v0}, Landroid/util/Base64;->encodeToString([BI)Ljava/lang/String;
move-result-object p1
return-object p1
.end method
|
5.再来写frida脚本,我不会写代码怎么办,直接问ai要一个,类名换成实际的com.zj.wuaipojie.util.Base64Utils。
var target_class = "com.zj.wuaipojie.util.Base64Utils";
var target_method = "encodeToString";
Java.perform(function () {
var TargetClass = Java.use(target_class);
TargetClass[target_method].overload('[B').implementation = function (p1) {
console.log("\n[+] Hooked encodeToString method");
var original_result = this[target_method](p1);
console.log("[+] Original p1 (input bytes): " + Array.from(p1).join(', '));
console.log("[+] Original result (encoded string): " + original_result);
var modified_result = "custom_base64_string";
console.log("[+] Modified result: " + modified_result);
return modified_result;
};
});
6.有了脚本,怎么样让他跑起来呢?不懂问ai啦,pip install frida-tools安装frida
1)、使用 adb 将 Frida Server 推送到模拟器
adb push frida64 /data/local/tmp/
adb shell chmod 755 /data/local/tmp/frida64
2)、在模拟器中启动 Frida Server
adb shell
cd /data/local/tmp
su
./frida64 &
3)、连接到模拟器并加载脚本(这里—U一直不识别模拟器,出于无奈通过ip来连接了)
frida -H 127.0.0.1:27042 -n wuaipojie -l hook_encodeToString.js
-U:连接到 USB 设备(模拟器被视为 USB 设备)。
-H 通过指定ip和端口来连接。
-n com.example.app:指定目标应用程序的包名。
-l hook.js:加载 Frida 脚本。
4)、出了一点小问题,frida版本不一致,重新下载弄一下,Frida Releases下载,在tag标签目录下找到frida-server-16.6.6-android-x86_64.xz下载。
5)、解决完之后一直提示shell不具有超级用户权限,找了一天才发现面具没给shell超级用户权限,难怪frida-server一直启动不了,。
6)、这里转发一下端口,让win系统能连接到这个端口
adb forward tcp:27042 tcp:27042
7.执行脚本,可以看成功hook到了返回值。
0x2 试试第八关
1.运行可以看到是这样的。
2.使用jadx搜索字符串定位到这里,可以看到调用了checkVip函数,双击跳转过去可以看到4个native关键字声明的函数,so文件名就是52pojie。
public static final void m75onCreate$lambda0(TextView textView, TextView textView2, TextView textView3, View view) {
if (SecurityUtil.checkVip()) {
textView.setText("是VIP啦!");
} else {
textView.setText("已过期");
}
textView2.setText(SecurityUtil.vipLevel("普通"));
textView3.setText(String.valueOf(SecurityUtil.diamondNum()));
}
public static native boolean check(String str);
public static native boolean checkVip();
public static native int diamondNum();
public static native String vipLevel(String str);
static {
System.loadLibrary("52pojie");
}
3.前面已经学过第一个参数就是JNIEnv,重新设置一下变量。
jstring __fastcall Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel(JNIEnv *a1, __int64 a2, void *a3)
{
char *v5; // x21
__int64 v6; // x0
__int128 v7; // q0
const char *v8; // x1
jstring v9; // x19
char v11[16]; // [xsp+8h] [xbp-58h] BYREF
void *ptr; // [xsp+18h] [xbp-48h]
__int128 v13; // [xsp+20h] [xbp-40h] BYREF
void *v14; // [xsp+30h] [xbp-30h]
__int64 v15; // [xsp+38h] [xbp-28h]
v15 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v5 = (char *)(*a1)->GetStringUTFChars(a1, a3, 0LL);
std::string::basic_string<decltype(nullptr)>((int)v11, v5);
v6 = std::string::append((int)v11, &unk_29285, 6u);
v7 = *(_OWORD *)v6;
v14 = *(void **)(v6 + 16);
v13 = v7;
*(_QWORD *)(v6 + 8) = 0LL;
*(_QWORD *)(v6 + 16) = 0LL;
*(_QWORD *)v6 = 0LL;
if ( (v11[0] & 1) != 0 )
operator delete(ptr);
(*a1)->ReleaseStringUTFChars(a1, a3, v5);
if ( (v13 & 1) != 0 )
v8 = (const char *)v14;
else
v8 = (char *)&v13 + 1;
v9 = (*a1)->NewStringUTF(a1, v8);
if ( (v13 & 1) != 0 )
operator delete(v14);
return v9;
}
4.继续练习frida,碰到这种看不懂代码的不要紧,打印一下参数和返回值看看
Interceptor.attach(Module.findExportByName("lib52pojie.so", "Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel"), {
onEnter: function (args) {
console.log("JNIEnv*: " + args[0]);
console.log("jobject/jclass: " + args[1]);
var jstringArg = args[2];
var env = Java.vm.getEnv();
var stringArg = env.getStringUtfChars(jstringArg, null);
console.log("jstring arg: " + stringArg);
env.releaseStringUtfChars(jstringArg, stringArg);
},
onLeave: function (retval) {
var env = Java.vm.getEnv();
var stringRet = env.getStringUtfChars(retval, null);
console.log("Return value: " + stringRet);
env.releaseStringUtfChars(retval, stringRet);
}
});
1)、打印参数和返回值,得到结果???看不懂啊,看来ai使用的姿势不对啊
JNIEnv*: 0xf3319380
jobject/jclass: 0xffffaf7c
jstring arg: 0xf30c2558
Return value: 0xec5a3310
2)、去问一下AI你写的代码为什么返回的是地址不是字符串
getStringUtfChars 返回的是一个指向 UTF-8 编码字符串的指针(NativePointer),你需要使用 Frida 的 readUtf8String 方法将其转换为字符串。
3)、修改一下hook脚本
Interceptor.attach(Module.findExportByName("lib52pojie.so", "Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel"), {
onEnter: function (args) {
var env = Java.vm.getEnv();
console.log("JNIEnv*: " + args[0]);
if (args[1] && !args[1].isNull()) {
var jobject = env.newLocalRef(args[1]);
var javaObject = Java.cast(jobject, Java.use("java.lang.Object"));
console.log("jobject/jclass class: " + javaObject.getClass().getName());
} else {
console.log("jobject/jclass is null or invalid");
}
if (args[2] && !args[2].isNull()) {
var stringPtr = env.getStringUtfChars(args[2], null);
var stringArg = stringPtr.readUtf8String();
console.log("jstring arg: " + stringArg);
env.releaseStringUtfChars(args[2], stringPtr);
} else {
console.log("args[2] is not a valid jstring");
}
},
onLeave: function (retval) {
if (retval && !retval.isNull()) {
var env = Java.vm.getEnv();
var stringPtr = env.getStringUtfChars(retval, null);
var stringRet = stringPtr.readUtf8String();
console.log("Return value: " + stringRet);
env.releaseStringUtfChars(retval, stringPtr);
} else {
console.log("Return value is not a valid jstring");
}
}
});
输出:
JNIEnv*: 0xf3319380
jobject/jclass class: java.lang.Class
jstring arg: 普通
Return value: 普通会员
4)、这里正己大佬提供了两种打印字符串的方法,备用
onEnter: function(args){
var jString = Java.cast(args[2], Java.use('java.lang.String'));
console.log("参数:", jString.toString());
var JNIEnv = Java.vm.getEnv();
var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();
console.log("参数:", originalStrPtr);
5.现在我们添加修改返回值的代码,再来执行一下
var newReturnValue = "菜鸟会员";
var newJString = env.newStringUtf(newReturnValue);
retval.replace(newJString);
var stringPtr = env.getStringUtfChars(newJString, null);
var stringRet = stringPtr.readUtf8String();
console.log("Modified return value: " + stringRet);
输出:
JNIEnv*: 0xf3319380
jobject/jclass class: java.lang.Class
jstring arg: 普通
Return value: 普通会员
Modified return value: 菜鸟会员
6.先学到这里吧,后面太复杂了渐渐看不懂了,感谢deepseek,感谢正己大佬的教程,AI真的强大,不需要自己写hook脚本了。
0x3 参考文档
1.《安卓逆向这档事》十三、是时候学习一下Frida一把梭了(上)
2.《安卓逆向这档事》十五、是时候学习一下Frida一把梭了(下)