安卓逆向 刷题笔记(1)
攻防世界 Mobile
ez_dex
- 这道题在网上看了好多 wp 基本都是手撕加密,但这题的 so 层加密挺不好分析的,而用frida-hook可以直接跳过解密,找到已经解密好的 dex 文件。
- 用 jadx 打开,在 AndroidManifest.xml 中找到关键信息:
android:hasCode="false"
,声明 APK 中不包含 Java/Kotlin 字节码(.dex 文件),表示所有逻辑都在 Native 层;当android:value="native"
时,系统会默认加载 libnative.so,此类应用的入口是 Native 层的android_main()
函数(而非 Java 的 Activity.onCreate() - 提取出 so 文件,用 ida 分析,直接搜索找到
android_main()
函数,首先注意到经过加密的filename
和name
,写一个脚本进行字符串解密
1 |
|
- 解密后发现得到了 dex 文件存放的位置,但是继续往下看,找到关键部分
用frida hook直接拿到解密后的 dex 文件
1 |
|
因为摇晃次数 v10 还直接控制数据解密流程,所以不能像满足的时间条件一样给 nop 掉
BGT(Branch if Greater Than) 是关键跳转,决定是否判定超时,NOP 掉 BGT(0x2866)会使程序永远不跳转,从而绕过时间检查
3.
有报错但 dex 文件是成功 dump 出来了的
但到这里也不能直接拿到因为没有权限,所以可以把它 pull 到普通目录比如 Download 里面后再存到本地,这样就能获取了
4. 再原 apk 中添加 dex 文件,加密就已经出来了
密钥:I have a male fish and a female fish
获取密文:
1 |
|
网站在线解密
qwb{TH3y_Io<e_EACh_OTh3r_FOrEUER}
easyjni
变种base64 算法逆向
- jadx 打开,看 MainACtivity ,关键方法:
a(String str)
方法
1 |
|
new a().a(str.getBytes()) 先对输入进行某种处理
然后调用 native 方法 ncheck() 进行验证ncheck(String str)
方法
1 |
|
声明了一个本地方法,实现在 libnative.so 中
查看 a 函数 ,可以分析推测出这是一个变种 base64
- 分析 so 文件, ida 打开,找到
Java_com_a_easyjni_MainActivity_ncheck
,
v5 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
这一行的等效代码为const char *input = (*env)->GetStringUTFChars(env, jstr, NULL);
,+ 676
是 GetStringUTFChars 的偏移量- 第一个循环 : 把输入字符串的前16位和后16位对调
1 |
|
- 第二个循环 : 把每一对字符前后对调
1 |
|
- 解题脚本
1 |
|
flag{just_ANot#er_@p3}
easyjava
算法逆向
jadx
打开,分析 MainActivity,主要逻辑大概是先提取中间部分(去除flag{}),然后对传入的每个字符调用a()方法,而a()方法中是先调用b.a(str),再调用a.a(结果)- 看 a 类和 b 类,主逻辑分别是将传入的整数返回对应的映射字符,以及传入的字符返回对应的映射整数
- 解密脚本
1 |
|
android 2.0
算法逆向
- 将apk文件放入
jadx
中,源代码里面找到MainActiviy
,找到关键部分函数
- 这段代码的功能:
这段代码实现了一个简单的 Android 应用,它有一个按钮、一个文本框和一个显示结果的文本。
用户在文本框(EditText)里输入一个字符串(可能是密码),
然后点击按钮时,代码会调用一个名为 JNI.getResult(str) 的本地方法来判断密码是否正确
如果 JNI.getResult(str) 返回 0,界面会显示 “Wrong”
如果 JNI.getResult(str) 返回 1,界面会显示 “Great”
点进JNI
关键部分是 JNI.getResult(str):
JNI.getResult(str) 是一个 本地方法,也就是说,它的实现并不在 Java 代码里,而是在 C/C++ 代码中,并且通过 JNI(Java Native Interface)被调用。
这意味着需要分析那个本地的.so文件
,找出这个getResult 方法的实现
2. 修改后缀,将.apk改成.zip,解压后找到.so文件,把libNative.so
用IDA
打开,找到几个关键函数
3.
- 分析
First
函数
还原:先异或0x80,再除以2
1 |
|
- 分析
Second
函数
直接异或还原
1 |
|
Third
同理
如果v6=a5就执行异或操作,所以v6=a5
1 |
|
- 由
Init
函数还原flag - Init函数是将输入的长度为15的字符串,每三个为一组,得到三组字符串(即刚刚解出的三组数据)
1 |
|
flag{sosorryla}
基础android
用misc方法解题了
- 将apk文件放入
010editor
中,发现文件头是50 4B 03 04是zip文件,修改后缀,解压 - 在assets文件夹下发现
time_2.zip
,放入010editor
,文件头是FF D8 FF,jpg文件,修改后缀,打开后是一张图片flag{08067-wlecome}
APK逆向
- 打开apk文件,用
jadx
打开,找到关键MainActiivty
,
注意到edit_sn
和edit_userName
,找到edit_userName = "Tenshine"
, - 在
CheckSN
中看到代码逻辑
md5加密,转为hex字符,for循环注意i+=2 - 解题脚本
1 |
|
flag为
bc72f242a6af3857a
APK逆向-2
- 用 jadx 打开后发现
AndroidManifest
反编译失败,改 apk 后缀为 zip ,解压后把AndroidManifest
放进 010editor ,发现有两处错误
第一处 : Chunk Type : 4 bytes,始终为 0x001c0001
第二处 : Unkown : 4 bytes,固定值,0x00000000 - 修改之后
- 把修改后的文件重新压缩,再改后缀 zip 为 apk ,重新放进jadx中打开可以看到
AndroidManifest
可以被成功反编译了,flag即出8d6efd232c63b7d2
app1
- 用
jadx
打开,MainAvtivity
里面发现有个versionCode
和versionName
,在BuildConfig
里面看到关键信息 - 将apk在模拟器上运行,会发现随便输入会弹出“再接再厉,加油~”,不输入会弹出“”年轻人不要耍小聪明噢”,结合代码梳理出逻辑:
先检查 inputString 是否满足 versionCode.charAt(i) ^ versionName 规则
再检查长度是否相等
两者同时满足,显示 “恭喜开启闯关之门!”
1 |
|
flag为
W3l_T0_GAM3_0ne
app3
- 下载附件后是一个 .ab 文件,这是安卓备份格式的文件,它分为加密和未加密两种类型,.ab 文件的前 24 个字节是类似文件头的东西,如果是加密的,在前 24 个字节中会有 AES-256 的标志,如果未加密,则在前 24 个字节中会有 none 的标志;对于没有加密的ab 文件,可以用以下指令解压
java -jar abp.jar unpack app3.ab app3.jar
解压后可以看到里面有一个 base.apk,还有两个数据库文件,用DB Browser打开发现是加密的,需要去找密钥 - 把app3.jar 直接用 jadx 打开,找到MainActivity,注意到在 OnCreate之后调用了函数a
1 |
|
- 加载SQLite数据库库文件
- 创建一个名为a的SQLiteOpenHelper子类实例
- 建ContentValues对象,用于存储要插入数据库的键值对,向contentValues添加键值对 name:Stranger,password:123456
- 实例化了一个com.example.yaphetshan.tencentwelcome.a.a类
- 调用aVar的a方法,传入name和password,返回一个字符串结果。点进去注意到这一块代码:取传入的两个字符串的前四位作为返回值,即得到”Stra”和”1234”
- 先调用aVar.b方法处理 a 和 password,然后与 a 拼接,再用aVar.a方法处理拼接后的字符串,然后取前七个字符作为数据库的密钥
获取密钥的脚本如下:
1 |
|
运行得出 KEY = ae56f99
3. 用这个密钥打开 Encryto.db ,浏览数据可以看到GN0ZntIM2xsMF9Eb19ZMHVfTG92M19UZW5jM250IX0=
,base64解码,
Tctf{H3ll0_Do_Y0u_Lov3_Tenc3nt!}