首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
10
8
[原创]CTF自毁程序密码:逆向分析
发表于: 2024-12-27 23:06
46049
[原创]CTF自毁程序密码:逆向分析
西贝Legion
2024-12-27 23:06
46049
### 一、 背景 这一题很有迷惑性,看似简单的代码逻辑,一眼看到的答案,其实并不是真相,重点在他的反检测。大多数的时候我们通过静态分析(java层还是so层)找到他的加密算法,再逆向还原其算法就能找到最终的答案,但是这道题不是,接下来我们我们会用到**frida、AndroidNativeEmu、unidbg、IDA动静态调试**。  ### 二、静态分析 #### java层分析 从代码可以看出,输入的密码调用方法securityCheck(String str),满足true则成功  #### so层静态分析 找到Java_com_yaotong_crackme_MainActivity_securityCheck函数 1. 初始化检查 byte_6359 和 byte_635A: 可能是静态变量,表示某些初始化是否已经完成。 sub_2494 和 sub_24F4: 两个子函数,分别在第一次执行时被调用,可能用于解密或初始化全局变量。 结果: 如果两个字节标志未设置,会调用这两个函数并设置标志位,确保只初始化一次。 2. 获取 Java 字符串的 UTF-8 值 v5 = (*a1)->GetStringUTFChars(a1, a3, 0); 3. 字符串比较 比较 Java 层传入的字符串 a3 是否与硬编码字符串 off_628C 匹配。如果匹配,返回 1,否则返回 0。  分析结果: v6 = off_628C; 就是我们需要的值,aWojiushidaan  但是我们输入该值到APP中提示是不正确的,那么由此可以猜测,必然是APP启动后,有程序修改了off_628C的值。 ### 三、AndroidNativeEmu模拟调用,验证下aWojiushidaan在静态下模拟调用,是否可以安全调用。 ``` import logging import sys from unicorn import UC_HOOK_CODE, UC_HOOK_MEM_READ, UC_HOOK_MEM_WRITE from unicorn.arm_const import * from androidemu.emulator import Emulator # Configure logging logging.basicConfig( stream=sys.stdout, level=logging.DEBUG, format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" ) logger = logging.getLogger(__name__) # Initialize emulator 实例化虚拟机 emulator = Emulator(vfp_inst_set=True) # 加载Libc库 emulator.load_library("../example_binaries/32/libc.so", do_init=False) # 加载要模拟的库 lib_module = emulator.load_library("libcrackme.so", do_init=False) # 定义内存回调函数以监控变量 0x12A0 的变化 target_address = 0x4450+0xa009b000 string_length =100 # 假设最大字符串长度为 32 字节 # Show loaded modules 打印已经加载的模块 logger.info("Loaded modules:") for module in emulator.modules: logger.info("[0x%x] %s" % (module.base, module.filename)) def memory_read_hook(uc, access, address, size, value, user_data): if address == target_address: # 获取当前值 # print(uc.mem_read(address, string_length).decode('ascii', errors='ignore')) current_value = uc.mem_read(address, string_length).split(b'\0', 1)[0].decode('ascii', errors='ignore') print(f"【READ】 Address: 0x{address:X}, Current Value: {current_value}") def memory_write_hook(uc, access, address, size, value, user_data): if address == target_address: # 获取写入的新值 new_value = uc.mem_read(address, string_length).split(b'\0', 1)[0].decode('ascii', errors='ignore') print(f"【WRITE】 Address: 0x{address:X}, New Value: {new_value}") # 注册指令和内存访问钩子 emulator.uc.hook_add( UC_HOOK_MEM_READ, # 捕获内存读取 memory_read_hook, None, target_address, target_address + string_length ) emulator.uc.hook_add( UC_HOOK_MEM_WRITE, # 捕获内存写入 memory_write_hook, None, target_address, target_address + string_length ) # 模拟运行函数 result1 = emulator.call_symbol( lib_module, 'Java_com_yaotong_crackme_MainActivity_securityCheck', emulator.java_vm.jni_env.address_ptr, 0, "wojiushidaan",//123 is_return_jobject=False ) # 输出结果 print("jnicheck result : {}".format(result1)) ``` 当我们分别输入 123 和 wojiushidaan 看返回的结果  结论:我们可以很确定v6 = off_628C值就是我们输入的值。 ### 四、解法(1)frida hook该函数。 在不考虑风控的前提下,明确了目标值用frida hook,是最快的方式,那就来吧,验证下。 ``` function hook_so() { Java.perform(function(){ var addr = Module.findBaseAddress("libcrackme.so"); var v1 = addr.add(0x4450); console.log(v1.readCString()); }); } function main() { hook_so() } setTimeout(main,4000) ```  得到值 : aiyou,bucuoo 输入该值验证成功 ### 五、解法(2)unidbg 模拟执行,打印关键参数 ``` package com.yaotong.crackme; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.arm.backend.DynarmicFactory; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.AbstractJni; import com.github.unidbg.linux.android.dvm.DalvikModule; import com.github.unidbg.linux.android.dvm.DvmObject; import com.github.unidbg.linux.android.dvm.VM; import com.github.unidbg.memory.Memory; import com.github.unidbg.pointer.UnidbgPointer; import java.nio.charset.Charset; import java.io.File; public class MainActivity extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; private final Module module; private final Memory memory; MainActivity() { // 创建模拟器 emulator = AndroidEmulatorBuilder.for32Bit().addBackendFactory(new DynarmicFactory(true)).build(); // 内存 memory = emulator.getMemory(); // 设置SDK memory.setLibraryResolver(new AndroidResolver(23)); // 创建虚拟机 vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/yaotong/crackme/you.apk")); //设置jni vm.setJni(this); //打印日志 vm.setVerbose(true); // 运行so文件 DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/yaotong/crackme/libcrackme.so"), true); //module module = dalvikModule.getModule(); // 调用JNI——onload // dalvikModule.callJNI_OnLoad(emulator); vm.callJNI_OnLoad(emulator, module); HookAddr(); } /** * 打印 Hex Dump 格式的数据 * * @param data 要打印的字节数组 */ private static void printHexDump(byte[] data) { int bytesPerLine = 16; // 每行打印16个字节 for (int i = 0; i < data.length; i += bytesPerLine) { // 打印当前行的地址(偏移量) System.out.printf("%08X ", i); // 打印当前行的十六进制数据 for (int j = 0; j < bytesPerLine; j++) { if (i + j < data.length) { System.out.printf("%02X ", data[i + j]); } else { System.out.print(" "); // 如果剩余字节不足16个,填充空格 } } System.out.print(" |"); // 打印当前行的字符内容(ASCII) for (int j = 0; j < bytesPerLine; j++) { if (i + j < data.length) { byte b = data[i + j]; if (b >= 32 && b <= 126) { System.out.print((char) b); // 打印可打印字符 } else { System.out.print("."); // 打印不可打印字符 } } else { System.out.print(" "); // 填充空格 } } System.out.println("|"); } } public static void main(String[] args) { MainActivity test = new MainActivity(); System.out.println(test.getSName()); } public void HookAddr() { // 目标地址,这里是示例地址 0x628C 0x4450 long targetAddress = module.base + 0x4450; // 使用 UnidbgPointer 来获取目标地址的数据 UnidbgPointer pointer = UnidbgPointer.pointer(emulator, targetAddress); // 读取目标地址的数据,假设它是一个字符串,长度为 16 字节 byte[] data = pointer.getByteArray(0, 16); // 读取16个字节 printHexDump(data); // 将读取的字节转换为字符串 // 将读取的字节转换为字符串,并指定正确的编码(例如 UTF-8 或 GBK) String value = new String(data, Charset.forName("GBK")); // 使用 UTF-8 编码 // 打印地址和读取的内容 System.out.println("Address: " + Long.toHexString(targetAddress)); System.out.println("Data at address: " + value); } //符号调用 public Boolean getSName() { // 创建一个vm对象 DvmObject<?> dvmObject = vm.resolveClass("com/yaotong/crackme/MainActivity").newObject(null); String input = "123"; // byte[] inputByte = input.getBytes(StandardCharsets.UTF_8); boolean success = dvmObject.callJniMethodBoolean(emulator, "securityCheck(Ljava/lang/string;)Z", input); System.out.println("[symble] Call the so md5 function result is ==> " + success); return success; } } ``` 执行验证结果如图所示:  通过模拟下调用(符号调用),在hook关键参数地址,运行后,获得的正确flag:aiyou,bucuoo,然后我们在输入flag 则显示true,成功!!! ### 六、解法(3)IDA动态调试 如果不喜欢用frida IDA动态调试也是比较通用直接的,可以一步一步跟踪查看代码的运行逻辑。 #### 调试前准备: 1. 查看getprop ro.debuggable的值:adb shell getprop ro.debuggable  2. Magisk 重置 ro.debuggable (重启失效) adb shell #adb进入命令行模式 magisk resetprop ro.debuggable 1  #### 开始动态调试  在Module list窗口(Debugger->Debugger windows->Module list)中找到libcrackme.so,双击它,f5进入伪代码页面,在目标函数下断点,v6 = off_C0B0E28C;   按下F9,弹出错误警告。  从这一系列的操作我们可以发现,wojiushidaan 这个值,在APP运行后,被重新赋值了。在进行调试的时候报错,说明有反调试存在。 #### IDA常见的反调试手段 1.端口检测:调试器在进行远程调试时,会占用一些固定的端口号。IDA Pro可以通过读取/proc/net/tcp文件,查找远程调试所用的23946端口,若发现该端口被占用,则说明进程正在被IDA调试 2.关键文件检测:通过修改android_server文件的名称,防止调试器找到并连接该文件 3.进程ID检测:在没有调试时,TracerPid为0;运行调试时,TracerPid会变为调试器的进程ID。通过修改系统调用函数,伪造TracerPid为0,以欺骗调试器 4.Java层反调试:在AndroidManifest.xml中设置android:debuggable="false",并在build.prop中设置ro.debuggable=0,防止应用在调试模式下运行。此外,可以通过检测Debug.isDebuggerConnected()方法的返回值来判断是否被调试 5.自我调试:父进程创建一个子进程,通过子进程调试父进程。这种方式消耗的系统资源较少,且能有效阻止其他进程调试受保护的进程。 #### 反反调试 我们要在so文件加载之前进行调试。这样就能判断程序大概检测位置,逐渐深入,定位问题的所在。 APK 重新打包成 debuuger 模式 ``` 1.apktool d -f app.apk -o app_source 2.修改 AndroidManifest.xml,找到解包后的 AndroidManifest.xml 文件,确认以下内容: 检查 android:debuggable 属性是否设置为 true,如果没有则添加: <application android:debuggable="true" ... > 如果已经有 android:debuggable 属性,直接改为 true。 3. 重新打包 APK apktool b app_source -o app_debuggable.apk 4.签名新 APK 使用 keytool 生成调试密钥(如果没有现成的密钥): keytool -genkey -v -keystore debug.keystore -alias debug -keyalg RSA -keysize 2048 -validity 10000 签名 APK: jarsigner -verbose -keystore debug.keystore -storepass android -keypass android app_debuggable.apk debug ``` adb shell am start -D -n com.yaotong.crackme/.MainActivity 以调试模式启动app,让程序停在加载so文件之前。  IDA设置如下:  连接jdb后,IDA运行绿色三角按钮,让程序把so文件加载出来。 ``` adb shell ps | findStr com.yaotong.crackme adb forward tcp:8855 jdwp:18518 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8855 ```   Ctrl+s 搜索进入我们的目标文件  进入JNI_Onload函数下断点进行调试  当前位置程序并没有奔溃,说明检测点还在后面,然后继续,跳到R7的时候报错了 那么检测点 位置找到了  把反调试的TracerPid 指令37 FF 2F E1 改为 00 00 00 00 就不会再有反调试的防护机制了  再进行正常调试。畅通无阻,顺利看到了off_D860B28C 的值 aiyou,bucuoo 
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#逆向分析
上传的附件:
自毁程序密码_1.0原版.apk
(261.18kb,33次下载)
收藏
・
10
点赞
・
8
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
PLEBFE
感谢你的贡献,论坛因你而更加精彩!
2025-8-2 01:45
mb_oeecmeou
为你点赞!
2025-3-24 11:12
Jtian
你的帖子非常有用,感谢分享!
2025-1-18 14:18
gostrmao
+1
你的帖子非常有用,感谢分享!
2025-1-13 10:14
zhczf
+1
谢谢你的细致分析,受益匪浅!
2025-1-3 20:40
zhengcai
感谢你分享这么好的资源!
2025-1-3 16:20
你瞒我瞒
谢谢你的细致分析,受益匪浅!
2024-12-30 09:26
信使君
+1
非常支持你的观点!
2024-12-29 23:56
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
4
)
你瞒我瞒
雪 币:
3421
活跃值:
(12739)
能力值:
( LV2,RANK:10 )
在线值:
发帖
4
回帖
269
粉丝
8
关注
私信
你瞒我瞒
2
楼
ida的截图好模糊
2024-12-30 09:27
0
西贝Legion
雪 币:
55
活跃值:
(7400)
能力值:
( LV6,RANK:90 )
在线值:
发帖
24
回帖
60
粉丝
125
关注
私信
西贝Legion
3
楼
你瞒我瞒
ida的截图好模糊
是有点,上传之前还挺清楚的,不知道是不是网站有压缩处理。
2024-12-30 10:07
0
像风一样呢
雪 币:
1713
活跃值:
(2051)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
39
粉丝
7
关注
私信
像风一样呢
4
楼
非常详细,谢谢分享!
2025-1-5 19:33
0
lemontree@me
雪 币:
6
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
11
粉丝
0
关注
私信
lemontree@me
5
楼
想问下大佬偏移0x4450怎么来的
2025-11-9 11:14
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
西贝Legion
24
发帖
60
回帖
90
RANK
关注
私信
他的文章
[原创]unidbg补环境实战(某文学网站)
6849
[原创]CTF自毁程序密码:逆向分析
46049
[原创]unidbg入门笔记
37634
[原创]CTF 丛林的秘密:算法分析
14055
[原创]MobileCTF easy系列合集:Flag算法还原
10477
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
huangyalei
zhongdong
吃饭去2
AvP9b
ONewTach
lemontree@me
3uiad
mb_twxozogd
Moky
小蜜蜂12138
YiGod
mb_tmbvnlxt
mb_tnkubkna
信使君
mb_fmpzmysp
npc0vo
mb_jcuaasqb
vahan
mb_osuntufu
mb_bobabzbw
mb_anasyysr
mb_kujsrqqv
mb_ynzcmbii
mb_lthgjpwj
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部