2021领航杯APK逆向 没见过这么急促、简陋的比赛
言归正传
本题反思: 反码,补码这些要会算,不能只会正数,负数也要会。
基础不牢啊,这道题如果算对了-16的二进制码就出了,300分可惜可惜
题目简单,但还是要以此为戒增强基础
一定要明白运算的含义,不能马虎
知识提要: APK文件要了解的基础
解压之后一般会出现以下文件(纯APK,无添加别的引擎)
AndroidManifest.xml 该文件是每个应用都必须定义和包含的,它描述了应用的名字、版本、权限、引用的库文件等等信息
1.META-INF目录下存放的是签名信息,用来保证apk包的完整性和系统的安全
2.保证了apk包里的文件不能被随意替换。如果想要替换里面的一幅图片,一段代码, 或一段版权信息,想直接解压缩、替换再重新打包,基本是不可能的。
3.软件修改后需要将里面的证书文件删除(.RSA、.SF、.MF三个文件)再重新签名,否则软件无法安装
res目录 1.res目录存放资源文件。包括图片,字符串等等。
2.res文件夹里存放的大部分是软件所需的资源及布局文件(drawable存放资源、layout、xml存放布局文件.xml),部分需要汉化的单词、语句会在这些.xml文件里
lib目录 存放一些so文件,有的可能没有
assets目录 存放一些配置文件,这些文件的内容在程序运行过程中可以通过相关的API获得
classes.dex文件 classes.dex是java源码编译后生成的java字节码文件(比如这个题就可以直接分析这个文件)
resources.arsc 编译后的二进制资源文件。resources.arsc文件是编译后的资源文件,大多数情况下,需要汉化的单词、语句绝大多数都在这个文件里,汉化 的时候首先就要看这个文件。
解题思路 这类简单apk首先就需要找到MainActive函数,这里我直接将class.dex文件用jadx打开了,不要管androidx开头的文件和google开头的文件,找最不一样的、出现次数最少的文件
这里轻松获得源码
看到最下面,找到关键判断
1 2 3 4 5 6 if (check.m39check_final_GBYM_sE(r1)) { Toast.makeText(this .this $0. this $0. getApplicationContext(),"Rightflag!" ,0 ).show(); } else { Toast.makeText(this .this $0. this $0. getApplicationContext(), "Wrong flag!" , 0 ).show() }
这里需要知道参数的含义
判断关键的“r1”就是由函数
1 check.m40enc_Fz0kQmc(substring, r4, r1);
得到的
该函数的三个参数很分析得到就是
输入的flag中间的内容;从一个文件中读取的几个字符;一个空字符
跳到这个函数的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public final void m40enc_Fz0kQmc (String str, byte [] bArr, byte [] bArr2) { Intrinsics.checkNotNullParameter(str, "input" ); Intrinsics.checkNotNullParameter(bArr, "key" ); Intrinsics.checkNotNullParameter(bArr2, "enc" ); byte [] r0 = UByteArray.m107constructorimpl(36 ); for (int i = 0 ; i <= 35 ; i++) { UByteArray.m118setVurrAj0(r0, i, UByte.m64constructorimpl((byte ) (str.charAt(i) ^ i))); } for (int i2 = 0 ; i2 <= 35 ; i2++) { UByteArray.m118setVurrAj0(bArr2, i2, UByte.m64constructorimpl((byte ) (UByteArray.m113getw2LRezQ(r0, 35 - i2) ^ UByteArray.m113getw2LRezQ(bArr, i2 % 16 )))); } for (int i3 = 0 ; i3 <= 35 ; i3++) { byte b = UByteArray.m113getw2LRezQ(bArr2, i3); UByteArray.m118setVurrAj0(bArr2, i3, UByte.m64constructorimpl((byte ) UnsignedUtils.m361uintDivideJ1ME1BU(UInt.m132constructorimpl(UByte.m64constructorimpl((byte ) (UByteArray.m113getw2LRezQ(bArr2, i3) & -16 )) & UByte.MAX_VALUE), 16 ))); UByteArray.m118setVurrAj0(bArr2, i3 + 36 , UByte.m64constructorimpl((byte ) (b & 15 ))); } }
看到主函数判断的函数
1 2 3 4 5 6 7 8 9 10 public final boolean m39check_final_GBYM_sE (byte [] bArr) { Intrinsics.checkNotNullParameter(bArr, "enc" ); for (int i = 0 ; i <= 71 ; i++) { if ("abcdefgh13462579" .charAt(UByteArray.m113getw2LRezQ(bArr, i) & UByte.MAX_VALUE) != "ccccebeebbeafbeeeabefabfaffffafaafaaea4b292he31922g6d54a62hchf2bb9ehagdc" .charAt(i)) { return false ; } } return true ; }
根据这个函数,可以直接求出r1的值(判断里的参数),但是这里需要注意的是,jadx不会对参数进行重命名,所以会出现参数名重复的现象,大坑
1 2 3 4 5 6 7 8 9 10 11 12 string_2="ccccebeebbeafbeeeabefabfaffffafaafaaea4b292he31922g6d54a62hchf2bb9ehagdc" string_1="abcdefgh13462579" bArr='123456789getflag' barr2=[] for i in range (len (string_2)): barr2.append((string_1.index(string_2[i]))&255 ) barr2_0=[] print (barr2)for i in range (36 ): barr2_0.append(barr2[i]*16 +barr2[i+36 ])
得到r1,也就是上面函数bArr2参数
这样就可以求出flag了,三个循环,第一个第二个没啥好分析的,直接逆向就行
1 2 3 4 5 6 7 8 9 r0=[0 for i in range (36 )] for i in range (36 ): r0[35 -i]=barr2_0[i]^ord (bArr[i%16 ]) print (r0)flag=[] for i in range (36 ): flag.append(chr (r0[i]^i))
看到第三个循环
1 2 3 4 5 6 7 8 9 10 for (int i3 = 0 ; i3 <= 35 ; i3++) { byte b = UByteArray.m113getw2LRezQ(bArr2, i3); UByteArray.m118setVurrAj0(bArr2, i3, UByte.m64constructorimpl((byte) UnsignedUtils.m361uintDivideJ1ME1BU(UInt.m132constructorimpl(UByte.m64constructorimpl((byte) (UByteArray.m113getw2LRezQ(bArr2, i3) & -16 )) & UByte.MAX_VALUE), 16 ))); UByteArray.m118setVurrAj0(bArr2, i3 + 36 , UByte.m64constructorimpl((byte) (b & 15 ))); }
这段加密的大体意思就是:
1 2 3 4 5 mov EAX, [ESP+I] and [ESP+I], FFFFFFF0 sal [ESP+I], 4 and EAX, 1111 mov [ESP+I+0x24], EAX
看似对对称的两个数进行操作,实际上还是一个数,给出脚本
1 2 for i in range (36 ): barr2_0.append(barr2[i]*16 +barr2[i+36 ])
组合起来就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 string_2="ccccebeebbeafbeeeabefabfaffffafaafaaea4b292he31922g6d54a62hchf2bb9ehagdc" string_1="abcdefgh13462579" bArr='123456789getflag' barr2=[] for i in range(len(string_2)): barr2.append((string_1.index(string_2[i]))&255) barr2_0=[] print(barr2) # print (barr2_0) for i in range(36): barr2_0.append(barr2[i]*16+barr2[i+36]) r0=[0 for i in range(36)] for i in range(36): r0[35-i]=barr2_0[i]^ord(bArr[i%16]) print(r0) flag=[] for i in range(36): flag.append(chr(r0[i]^i)) print(''.join(flag))
APK例外 也不算是例外,只是我觉得是例外,我做的题太少了……
apk游戏,buu的PixelShooter
这是一个由unity引擎支持的一个打飞机游戏,他的flag存在于
assets\bin\Data\Managed目录下的
Assembly-CSharp.dll 文件中