首先反编译classes.dex文件,结果如下:
只有Application类,显然是核心dex文件被保护了。
第一反应是拿出zjdroid进行动态backsmali,但一执行命令
adb shell am broadcast -a com.myzjdroid.invoke --ei target 2282 --es cmd "{\"action\":\"dump_dexinfo\"}"
程序就退出了,显然针对zjdroid做了防护,那么下一步就是看看是否有可能去掉防护。
用IDA看了看so,发现so加入了大量的垃圾代码,看起来好费劲。得慢慢来了。
使用AXMLPrinter2.jar反编译AndroidManifest.xml,发现了搞鬼的代码:
<receiver
android:name="com.zjdroid.invoke"
>
<intent-filter
>
<action
android:name="com.zjdroid.invoke"
>
</action>
</intent-filter>
</receiver>
注册了与zjdroid相同的广播,尝试修改zjdroid源代码,把广播换个名字。结果是:
虽然程序不退出了,但zjdroid始终收不到广播,命令不执行。那就对zjdroid进行个大换血。我的做法是新建了个xposed工程,将zjdroid的dump代码移置到新工程中,这个工作大概消耗了半天的时间。最后hook了android.app.Application的onCreate方法,在这个点进行dex的dump。
首先打印所有的dex信息。发现有2个,如下:
dexFile:/data/app/crackme.a3-1.apk cookie:1565641824
dexFile:/data/data/crackme.a3/lib/libmobisecy.so cookie:1552628144
第1个是classes.dex,第2个就是核心dex了
最终成功dump出核心dex文件。截图如下:
下面的工作就是java逆向了。
使用JAD查看代码,得到代码逻辑如下:
(1) 将输入的字符通过密码表转化,密码表如下:
static
{
a("a", ". _");
a("b", "_ . . .");
a("c", "_ . _ .");
a("d", "_ . .");
a("e", ".");
a("f", ". . _ .");
a("g", "_ _ .");
a("h", ". . . .");
a("i", ". .");
a("j", ". _ _ _");
a("k", "_ . _");
a("l", ". _ . .");
a("m", "_ _");
a("n", "_ .");
a("o", "_ _ _");
a("p", ". _ _ .");
a("q", "_ _ . _");
a("r", ". _ .");
a("s", ". . .");
a("t", "_");
a("u", ". . _");
a("v", ". . . _");
a("w", ". _ _");
a("x", "_ . . _");
a("y", "_ . _ _");
a("z", "_ _ . .");
a("2", ". _ _ _ _");
a("1", ". . _ _ _");
a("3", ". . . _ _");
a("4", ". . . . _");
a("0", ". . . . .");
a("6", "_ . . . .");
a("9", "_ _ . . .");
a("8", "_ _ _ . .");
a("7", "_ _ _ _ .");
a("5", "_ _ _ _ _");
}
(2) 将转换后的字符交于b类的public void run()函数处理,加入了大量的混淆代码,其中调用的CRC32、sha1、AES,但都是骗人的,出题人真是太坏了,我一直怀疑自己的dump代码是否有问题,但最后还是找到了真正的代码逻辑。
核心代码片段如下:
ac = s.toCharArray();
i = s.substring(0, 2).hashCode();
if(i <= 3904)
首先头两个字符的hashCode要小于3904
if(i != 3618) goto _L8; else goto _L7
确切的就是要等于3618
if(ac[0] + ac[1] != 168) goto _L8; else goto _L9
字符之和为168,通过枚举确定前2个字符为“s5”
L9:
abyte1 = (new StringBuilder()).append(((f)e.getAnnotation(f)).a()).append(((f)a.getAnnotation(f)).a()).toString().getBytes();
if(-2 + ac.length != abyte1.length) goto _L11; else goto _L10
下面的逻辑就是剩余的字符要等于(f)e.getAnnotation(f)).a()和(f)a.getAnnotation(f)).a()的字符串拼接。查看e和a这两个类,找到了@f(a="7e")和@f(a="1p")
那么字符串就是s57e1p
(3) 通过密码表转化,得到答案:... _____ ____. . ..___ .__.
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)