题目

CSDN免积分下载

查壳

先查壳(PKiD等),有壳脱壳,没壳用AndroidKiller等反编译工具打开查看JAVA代码
无壳APK

Java Decompiler - MainActivity

看起来代码和字符串都比以前的题目多很多,并且主活动调用“startActivity”启动新活动:
image.png

答案

xman{201608Am!2333}

Writeup

解题

运行

只有图片+“自由正义分享”的按钮:
image.png
点击按钮后弹框+两个选项。选“不玩了”后退出APP:
image.png
点击“注册”要求注入注册码,先猜一下是注册码破解:
image.png
不能为空,输入字符串后弹框提示为“您的注册码已保存”,然后退出APP:
image.png

解题思路

搜索字符串

使用反编译工具的字符串搜索功能,搜索认为比较重要的代码部分的提示,由于“您的注册码已保存”是中文,需要转为UniCode进行搜索:
image.png
这是12道题的倒数第二题,终于给我找到不在主活动里的提示字符串了🤧

MainActivity

搜不出来或者没有思路时,应该先看Android程序的入口——也就是MainActivity主活动。
做题之前先运行和操作了一下APP,知道有按钮,选中Java代码中按钮实例化对象,查看其被调用部分的代码,在onClick内有判断逻辑,成立条件为进入doRegister方法:
image.png

代码逻辑

上述的解题思路从主活动处开始比较稳妥,从字符串搜索到的其他活动处入手可能更快。

MainActivity-doRegister

  1. paramAnonymousDialogInterface.setComponent(new ComponentName("com.gdufs.xman", "com.gdufs.xman.RegActivity"));
  2. MainActivity.this.startActivity(paramAnonymousDialogInterface);
  3. MainActivity.this.finish();
  1. 实例化一个Intent对象,setComponent设置用于处理意图的应用程序组件的名称为“com.gdufs.xman.RegActivity”;
  2. startActivity启动1中的Intent对象;
  3. 结束主活动本身

    活动RegActivity

    image.png

  4. 检测输入框是否为空;

  5. 不为空将输入框的数据传入“(MyApp)RegActivity.this.getApplication()).saveSN”方法;
  6. 弹框提示“您的注册码已保存”后杀死当前进程。

    1. public void onClick(View paramAnonymousView)
    2. {
    3. paramAnonymousView = RegActivity.this.edit_sn.getText().toString().trim();
    4. if ((paramAnonymousView == null) || (paramAnonymousView.length() == 0))
    5. {
    6. Toast.makeText(RegActivity.this, "您的输入为空", 0).show();
    7. }
    8. for (;;)
    9. {
    10. return;
    11. ((MyApp)RegActivity.this.getApplication()).saveSN(paramAnonymousView);
    12. new AlertDialog.Builder(RegActivity.this).setTitle("回复").setMessage("您的注册码已保存").setPositiveButton("好吧", new DialogInterface.OnClickListener()
    13. {
    14. public void onClick(DialogInterface paramAnonymous2DialogInterface, int paramAnonymous2Int)
    15. {
    16. Process.killProcess(Process.myPid());

    saveSN方法

    1. public class MyApp
    2. extends Application
    3. {
    4. public static int m = 0;
    5. static
    6. {
    7. System.loadLibrary("myjni");
    8. }
    9. public native void initSN();
    10. public void onCreate()
    11. {
    12. initSN();
    13. Log.d("com.gdufs.xman m=", String.valueOf(m));
    14. super.onCreate();
    15. }
    16. public native void saveSN(String paramString);
    17. public native void work();
    18. }

    System.loadLibrary动态加载库文件

    System.loadLibrary(String libname)则只会从指定lib目录下查找,并加上lib前缀和.so后缀

System.loadLibrary("myjni");

相当于libmyjni.so,这时候到APK解压路径\lib下查看,发现多个子文件夹,这是根据不同CPU架构兼容性创建的,随便挑一个分析即可(建议armeabi-v7a):
image.png

早期的Android系统几乎只支持ARMv5的CPU架构,目前支持七种CPU架构:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起),每一种都关联着一个相应的ABI。对应的ABI依次是:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64,对应Android app工程中so库的不同目录。

IDA+SO

查壳为32位无壳ELF,拖入IDA反编译,搜索“saveSN”方法——🈚
因为静态注册的方法都是按照JNI命名规则(Java_packagename_classname_methodname(JNIEnv *env,jclass/jobject,…) )命名的,搜不到就是动态注册的方法

静态注册与动态注册的区别

区别是效率。
静态注册,每次使用native方法时,都要去寻找;而动态注册,由于表的存在,查找效率高。

静态注册

优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低
缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高

动态注册

优点: 灵活性高, 更改类名,包名或方法时, 只需对更改模块进行少量修改, 效率高
缺点: 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败

JNI_OnLoad方法

image.png

已知:

  1. 在加载动态库后,执行JNI_OnLoad方法动态注册
  2. JNI_OnLoad方法正向开发的代码和数据类型

在IDA中将部分参数进行类型修改和重命名:
image.png
RegisterNatives方法(JNI环境提供的用于注册Native方法的方法)将会注册3个方法:
image.png

initSN

读取“/sdcard/reg.dat”的数据检测出是否为“EoPAoY62@ElRD”,如果成功调用“setValue”:
image.png
查看“setValue”的交叉引用,除了对比为一致的情况下为1,失败的情况都为0。认为该值应该是字符串对比的标志位。检查Java对initSN方法的调用,其值对应“m”:
image.png
运行APP输入“EoPAoY62@ElRD”,依旧显示“注册码已保存”,但是.dat文件的内容被写入“201608Am!2333”:
image.png

saveSN

检测输入的数据是否满足加密条件,数据经过加密处理后会写入“/sdcard/reg.dat”保存:
image.png

加密算法

根据IDA的还原可知,加密算法的key是“W3_arE_whO_we_ARE”中3个字符串,将key和输入的数据进行异或加密。
由于异或算法是一种对称加密算法,key值可以通过第一个do+While(”W3_arE_whO_we_ARE”)的方式算出,也可以通过明文还原算出。

对称加密 明文 → 加密 → 密文 密文 → 加密 → 明文

输入明文333,“/sdcard/reg.dat”中保存密文“DlR”,将明文再次用密文加密(明文 Xor 密文)可得密钥为“w_a”:
image.png
使用密钥“w_a”对“EoPAoY62@ElRD”进行异或&input=RW9QQW9ZNjJARWxSRA),得“201608Am!2333”:
image.png

work

主要用作输出提示,通过SN检测标志位打印出提示“输入即是flag,格式为xman{……}!”:
image.png

⚠⚠⚠危险危险危险⚠⚠⚠

在APP内输入“201608Am!2333”并没有特殊提示,但是这时“/sdcard/reg.dat”已经写入了“EoPAoY62@ElRD”。
需要再次(成立条件:“/sdcard/reg.dat”内有数据“EoPAoY62@ElRD”)打开APP,重新触发检测注册码的流程,才会提示“输入既是flag,格式为xman{……}!”
image.png
不过做完后复盘感觉不用这么麻烦,看完“initSN”后可以判断注册成功的逻辑就是,“/sdcard/reg.dat”内有数据“EoPAoY62@ElRD”。
在网上搜“破解版”的时候,很多软件只需要“替换”某文件(内含成功的注册码信息),也是这个道理。
如果需要输入的情况,则是还原算法,写注册机。