当前位置: 首页 > news >正文

Writeup:AliCrackme2

关键代码定位

image-20260130215511262

image-20260130215559258

字符串搜索"验证码校验失败"

image-20260130220252523

package com.yaotong.crackme;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;/* loaded from: classes.dex */
public class MainActivity extends Activity {public Button btn_submit;public EditText inputCode;
//声明native层方法securityCheckpublic native boolean securityCheck(String str);@Override // android.app.Activityprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);getWindow().setBackgroundDrawableResource(R.drawable.bg);this.inputCode = (EditText) findViewById(R.id.inputcode);this.btn_submit = (Button) findViewById(R.id.submit);this.btn_submit.setOnClickListener(new View.OnClickListener() { // from class: com.yaotong.crackme.MainActivity.1@Override // android.view.View.OnClickListenerpublic void onClick(View v) {//获取输入的内容,并赋值给resultString result = MainActivity.this.inputCode.getText().toString();//调用securityCheck方法判断输入内容是否正确if (MainActivity.this.securityCheck(result)) {Intent i = new Intent(MainActivity.this, (Class<?>) ResultActivity.class);MainActivity.this.startActivity(i);} else {Toast.makeText(MainActivity.this.getApplicationContext(), "验证码校验失败", 0).show();}}});}
//加载了crackme库static {System.loadLibrary("crackme");}
}

关键代码分析

IDA查看so层代码,查看导出表,关键代码在Java_com_yaotong_crackme_MainActivity_securityCheck

image-20260130221438250

int __fastcall Java_com_yaotong_crackme_MainActivity_securityCheck(int a1, int a2, int a3)
{unsigned __int8 *v5; // r0char *v6; // r2int v7; // r3int v8; // r1if ( !byte_CD70F359 ){sub_CD70B494(byte_CD70F304, 8, &unk_CD70D46B, &unk_CD70D468, 2, 7);*((_BYTE *)&unk_CD70F085 + 724) = 1;}if ( !*((_BYTE *)&unk_CD70F086 + 724) ){sub_CD70B4F4(byte_CD70F36C, 25, &unk_CD70D530, &unk_CD70D474, 3, 117);*((_BYTE *)&unk_CD70F086 + 724) = 1;}_android_log_print(4, byte_CD70F304, byte_CD70F36C);//Java层MainActivity.this.securityCheck(result)->Java_com_yaotong_crackme_MainActivity_securityCheck(int a1, int a2, int a3),result->v5v5 = (unsigned __int8 *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);v6 = off_CD70F28C;//逐字节比较输入字符串与off_628C指向的字符串while ( 1 ){v7 = (unsigned __int8)*v6;if ( v7 != *v5 )break;++v6;++v5;v8 = 1;if ( !v7 )return v8;}return 0;
}

image-20260131191137956

但是flag并不是wojiushidaan

image-20260131200606529

下断点调试查看

image-20260131210740976

进程直接退出,应该是有反调试。Exit Code -9:这表示进程收到了一个信号,信号编号为9。在Linux中,信号9是SIGKILL,它是一个无法捕获和忽略的强制终止信号。当进程接收到SIGKILL信号时,它会立即终止,不允许进行任何清理或资源释放。

image-20260131211803808

securityCheck函数中没有能起到反调试功能的代码,也没有加载其他库、声明其他native层函数,回去重新看了一眼java层也没有能起到反调试功能的代码,只能是JNI_OnLoad了

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{int *v3; // r5int v4; // r6int *v5; // r6jint v6; // r6int v8; // [sp-8h] [bp-28h] BYREFchar v9[8]; // [sp+0h] [bp-20h] BYREFdword_C65E02C8 = 0;v3 = (int *)dword_C65E02C4;if ( dword_C65E02C4 ){do{if ( *v3 >= 1 ){v4 = 0;do((void (*)(void))v3[++v4])();while ( v4 < *v3 );}v5 = (int *)v3[11];free(v3);v3 = v5;}while ( v5 );*(&abort_ptr + 181) = 0;}((void (__fastcall *)(char *, _DWORD, void (__noreturn *)(), _DWORD, _DWORD))*(&dlsym_ptr + 181))(v9,0,sub_C65DB6A4,0,0);sub_C65DB7F4();v6 = 65540;if ( (*vm)->GetEnv(vm, (void **)&v8, 65540) )return -1;return v6;
}

但是在JNI_OnLoad处下的断点还是没有断下来,没办法动态分析反调试逻辑了,只能根据静态代码揣测一下了

反调试逻辑一:

通过 dlsym动态获取 pthread_create创建线程,执行子线程sub_C65DB6A4,循环检查 /proc/self/status的 TracerPid字段。若非零(表示被调试),触发自杀(如 raise(SIGKILL))

((void (__fastcall *)(char *, _DWORD, void (__noreturn *)(), _DWORD, _DWORD))*(&dlsym_ptr + 181))(v9,0,sub_C65DB6A4,0,0);
void __noreturn sub_C65DB6A4()
{while ( 1 ){sub_C65DB30C();off_C65E02B0(3);}
}
// 函数入口:动态解析系统函数地址并注册析构函数,实现TracerPid检测与内存混淆
int sub_C65DB30C()
{int v0; // r8: 动态解析的fopen函数地址void (__fastcall *v1)(_BYTE *, void *, int); // r4: 文件操作函数指针(fopen/fclose)int (__fastcall *v2)(_BYTE *, void *); // r5: _cxa_atexit函数地址(注册析构函数)unsigned int v3; // r6: 临时变量(内存偏移/状态标记)char *v4; // r4: 缓冲区指针(用于字符串操作)int (__fastcall *v5)(_BYTE *, void *); // r5: 动态解析的fgets函数地址char *v6; // r11: GOT表操作指针(Hook函数地址)unsigned int v7; // r4: 文件读取长度(覆盖整个status文件)char *v8; // r6: GOT表操作指针(混淆内存地址)void (__fastcall *v9)(_BYTE *, void *, char *, int *); // r11: 动态解析的sscanf函数地址// 初始化临时缓冲区(清空残留数据)memset(v15, 0, sizeof(v15)); // 动态解析fopen地址(通过GOT表偏移)v0 = off_C65E0294();  // 强制类型转换为文件操作函数指针(规避静态分析)v1 = (void (*)(...))off_C65E0298;  // 反调试标记初始化(首次执行时修改内存关键数据)if ( !byte_C65E035B ) {sub_C65DC39C(&byte_C65E0349, 16, &unk_C65DE550, "s!#L", 4, 87); // 修改内存中的"/proc/self/status"路径*((_BYTE *)&unk_C65E0087 + 724) = 1; // 设置执行标记}// 调用fopen打开/proc/self/status文件(动态解析函数地址)v1(v15, &unk_C65E0349, v0);  // 注册析构函数(确保程序退出时关闭文件描述符)v2 = (int (*)(...))*(&_cxa_atexit_ptr + 181);  if ( !*((_BYTE *)&unk_C65E0088 + 724) ) {sub_C65DC54C(&unk_C65E0290, 2, &unk_C65DE54A, &unk_C65DE4FC, 0, 1); // 修改内存中的"TracerPid:"关键字*((_BYTE *)&unk_C65E0088 + 724) = 1; // 设置标记}// 注册析构函数(自动调用fclose)v11 = v2(v15, &unk_C65E0290);  // 清空文件读取缓冲区memset(v14, 0, sizeof(v14));  v3 = 0x2D4u; // 读取长度(0x2D4=724字节,覆盖整个status文件)// 动态解析fread地址并读取文件内容(规避静态分析)if ( ((int (*)(...))off_C65E02A0)(v14, 512, v11) )  // 主循环:持续检测TracerPid值{// 动态解析fgets地址(Hook文件读取操作)v5 = (int (*)(...))off_C65E02A4;  if ( v5(v14, &unk_C65E0325) ) { // 逐行读取(匹配"TracerPid:")memset(v4, 0, 0x80u); // 清空临时缓冲区// 动态解析sscanf地址(解析TracerPid数值)v9 = (void (*)(...))off_C65E02A8;  if ( !byte_C65E035E ) {sub_C65DC39C((char *)&GLOBAL_OFFSET_TABLE_ + v3 + 65, 6, &unk_C65DE461, "L79", 3, 147); // Hook atoi函数byte_C65E008A[v3] = 1; // 设置标记}// 解析TracerPid值(返回非零则触发反调试)v9(v14, &unk_C65E02D1, v4, &v12);  if ( v12 >= 1 ) break;  }// 动态解析fread地址继续读取下一行if ( !((int (*)(...))off_C65E02A0)(v14, 512, v11) )  return _stack_chk_guard - v16; // 文件读取失败触发栈保护崩溃}// 反调试触发:修改_stack_chk_fail_ptr偏移地址调用abort()(*(void (*)(int, int))((char *)&_stack_chk_fail_ptr + v3))(v0, 9);  // 返回干扰值(避免被识别为反调试函数)return _stack_chk_guard - v16;  
}

反调试逻辑二:

v6 = 65540;if ( (*vm)->GetEnv(vm, (void **)&v8, 65540) )return -1;return v6;

JNI_OnLoad 中通过 GetEnv 检测调试器的实现原理与流程

在 Android Native 层,JNI_OnLoad 是本地库加载时 JVM 调用的入口函数。其反调试机制的核心逻辑如下:

  1. GetEnv 的调用时机
    • 正常流程:Java 层调用 System.loadLibrary 加载本地库时,JVM 自动调用 JNI_OnLoad,此时 GetEnv 由 JVM 内部调用,返回主线程的 JNIEnv。

    • 调试器附加场景:调试器(如 IDA、GDB)通过 ptrace 附加到进程后,会主动调用 GetEnv 初始化 JNI 环境以附加调试器。此时 GetEnv 返回非零值(成功获取 JNIEnv)。

  2. 反调试检测逻辑

    jint JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv* env;// 检测 GetEnv 是否被调试器调用if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6)) {return -1;  // 返回错误码,阻止 JNI 环境初始化}// 正常注册 JNI 方法return JNI_VERSION_1_6;
    }
    

    • 检测条件:若 GetEnv 返回非零值(即调试器已附加),JNI_OnLoad 立即返回 -1,导致 JNI 环境初始化失败。

    • 崩溃触发:JVM 加载本地库失败,应用进程直接崩溃,阻止调试器继续执行。

反反调试

没招了,直接上脚本吧

//FridaContainer
(function(){
function klog(data,...args){for (let item of args){data+="\t"+item;}var message={};message["jsname"]="anti_debug";message["data"]=data;send(message);
}function tag_klog(tag,data){klog(tag+"\t"+data);
}function showNativeStacks(context) {tag_klog('showNativeStacks', '\tBacktrace:\n\t' + Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t'));
}function getLR(context) {if (Process.arch == 'arm') {return context.lr;}else if (Process.arch == 'arm64') {return context.lr;}else {tag_klog('getLR', 'not support current arch: ' + Process.arch);}return ptr(0);
}function getModuleByAddr(addr) {var result = null;Process.enumerateModules().forEach(function (module) {if (module.base <= addr && addr <= (module.base.add(module.size))) {result = JSON.stringify(module);return false; // 跳出循环}});return result;
}function anti_fgets() {const tag = 'anti_fgets';const fgetsPtr = Module.findExportByName(null, 'fgets');tag_klog(tag, 'fgets addr: ' + fgetsPtr);if (null == fgetsPtr) {return;}var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {if (null == this) {return 0;}var logTag = null;// 进入时先记录现场const lr = getLR(this.context);// 读取原 buffervar retval = fgets(buffer, size, fp);var bufstr = buffer.readCString();if (null != bufstr) {if (bufstr.indexOf("TracerPid:") > -1) {buffer.writeUtf8String("TracerPid:\t0");logTag = 'TracerPid';}//State:	S (sleeping)else if (bufstr.indexOf("State:\tt (tracing stop)") > -1) {buffer.writeUtf8String("State:\tS (sleeping)");logTag = 'State';}// ptrace_stopelse if (bufstr.indexOf("ptrace_stop") > -1) {buffer.writeUtf8String("sys_epoll_wait");logTag = 'ptrace_stop';}//(sankuai.meituan) telse if (bufstr.indexOf(") t") > -1) {buffer.writeUtf8String(bufstr.replace(") t", ") S"));logTag = 'stat_t';}// SigBlkelse if (bufstr.indexOf('SigBlk:') > -1) {buffer.writeUtf8String('SigBlk:\t0000000000001204');logTag = 'SigBlk';}// fridaelse if (bufstr.indexOf('frida') > -1) {buffer.writeUtf8String("");logTag = 'frida';}if (logTag) {tag_klog(tag + " " + logTag, bufstr + " -> " + buffer.readCString() + ' lr: ' + lr+ "(" + getModuleByAddr(lr) + ")");showNativeStacks(this?.context);}}return retval;}, 'pointer', ['pointer', 'int', 'pointer']));
}function anti_ptrace() {var ptrace = Module.findExportByName(null, "ptrace");if (null != ptrace) {ptrace = ptrace.or(1);tag_klog('anti_ptrace', "ptrace addr: " + ptrace);// Interceptor.attach(ptrace, {//     onEnter: function (args) {//         DMLog.i('anti_ptrace', 'entry');//     }// });Interceptor.replace(ptrace.or(1), new NativeCallback(function (p1, p2, p3, p4) {tag_klog('anti_ptrace', 'entry');return 1;}, 'long', ['int', "int", 'pointer', 'pointer']));}
}function anti_fork() {var fork_addr = Module.findExportByName(null, "fork");tag_klog('anti_ptrace', "fork_addr : " + fork_addr);if (null != fork_addr) {// Interceptor.attach(fork_addr, {//     onEnter: function (args) {//         DMLog.i('fork_addr', 'entry');//     }// });Interceptor.replace(fork_addr, new NativeCallback(function () {tag_klog('fork_addr', 'entry');return -1;}, 'int', []));}
}function anti_exit() {const exit_ptr = Module.findExportByName(null, 'exit');tag_klog('anti_exit', "exit_ptr : " + exit_ptr);if (null == exit_ptr) {return;}Interceptor.replace(exit_ptr, new NativeCallback(function (code) {if (null == this) {return 0;}var lr = getLR(this.context);tag_klog('exit debug', 'entry, lr: ' + lr);return 0;}, 'int', ['int', 'int']));
}function anti_kill() {const kill_ptr = Module.findExportByName(null, 'kill');tag_klog('anti_kill', "kill_ptr : " + kill_ptr);if (null == kill_ptr) {return;}Interceptor.replace(kill_ptr, new NativeCallback(function (ptid, code) {if (null == this) {return 0;}var lr = getLR(this.context);tag_klog('kill debug', 'entry, lr: ' + lr);showNativeStacks(this.context);return 0;}, 'int', ['int', 'int']));
}function anti_debug() {anti_fgets();anti_exit();anti_fork();anti_kill();anti_ptrace();
}Java.perform(function() {klog("init","anti_debug.js init hook success")anti_debug();
});})();

成功反反调试,得到flag:aiyou,bucuoo

image-20260212172945688

附加反反调试脚本之后,在JNI_OnLoad处下的断点还是没有触发,不懂

http://www.jsqmd.com/news/375305/

相关文章:

  • Day19
  • 基于深度学习YOLOv8的密集行人检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)
  • 基于深度学习YOLOv10的安全背心穿戴识别检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)
  • 市面上专业的2026板材十大品牌有哪些 - 品牌推荐(官方)
  • 基于springboot和vue框架的中小学学生成绩查询系统
  • 基于深度学习YOLOv10的杂草检测系统(12种)(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)
  • 面板数据模型:区域与企业经济分析的实证工具
  • AI应用架构师必知:AI模型监控与告警的核心要点
  • 相机标定(Calibration)01-4:相机标定中不同标定板的优缺点【棋盘格、圆点阵列、ArUco标记板、AprilTag标记板、3D标定物】
  • 基于深度学习YOLOv10的篮球运动员检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)
  • Spark + 数据湖:构建实时分析管道的5个关键技巧
  • 陀螺仪和加速度计(模拟状态,计算运动状态)
  • 提示工程性能测试框架对比:主流工具的性能、易用性、扩展性全测评
  • 数据治理标准化:ISO 38505在大数据环境下的应用
  • 市面上2026板材厂家排名 - 品牌推荐(官方)
  • 基于深度学习YOLOv10的密集行人检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)
  • 基于深度学习YOLOv10的可回收塑料识别分类检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)
  • 深入解析:MySQL中,binlog文件开头的Previous_gtids_log_event是如何计算的
  • nc is qt
  • 基于深度学习YOLOv8的小目标车辆检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)
  • NOIWC 2026 游继
  • 靠谱的2025板材厂家排名 - 品牌推荐(官方)
  • 市面上专业的2025板材工厂排名 - 品牌推荐(官方)
  • 2/12 状压dp枚举子集类问题练习
  • Vim命令
  • 如何解决 Vue 项目启动时出现的 “No such module: http_parser” 错误问题:从0到1避坑指南(附完整代码)
  • 《计算机是怎样跑起来的》——让程序像流水一样流动
  • 2026板材工厂排行榜 - 品牌推荐(官方)
  • 大数据诊断性分析:从数据采集到结果可视化的全流程
  • 2026 年四川优质仿真恐龙厂家推荐榜:以科技赋能文旅,凭实力领跑仿真恐龙场景体验 - 深度智识库