本文首发于博主公众号LISTONE,欢迎关注哦!
这个题分析起来逻辑比上个easyjava要简单许多,做题速度快了很多。
首先还是用jeb打开,打开后首先看这个配置文件(对于这道比较简单的题来说,不看也可,不过为了培养一下好的习惯,看一下比较好)。
然后我们点击那个 dex
,可以看到反编译之后的目录结构了,根据上面那个 name
属性,我们就可以找到主文件。
看到它调用了 MainActivity.a
方法,我们继续跟进去看看这个 a
方法干了什么。
可以看到它有调用了另外一个 a
方法,去分析它。
在第二个 a
方法中可以看到有一个关键语句
v0_1 = this.ncheck(new a().a(arg3.getBytes()));
先实例化了一个 a
类的对象,然后调用了 ncheck
,这个ncheck
是我们的 so
文件里的一个函数。
我们先来看这个 a
类的java代码,先对它分析一下,我们再去看 so
文件。
我们主要分析 a
函数,看到有一个 a.a
静态变量,这时我们猜测,这个是否是变种的base64编码,我们写一个java文件来调用一下这个类里的 a
函数,进行测试一下
public class test{
public static void main(String[] args){
String s = "listone";//对listone进行变种base64编码
System.out.println(new a().a(s.getBytes()));
}
}
class a {
private static final char[] a = {'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N'};
public a() {
super();
}
public String a(byte[] arg10) {
int v8 = 3;
StringBuilder v4 = new StringBuilder();
int v0;
for(v0 = 0; v0 <= arg10.length - 1; v0 += 3) {
byte[] v5 = new byte[4];
int v3 = 0;
byte v2 = 0;
while(v3 <= 2) {
if(v0 + v3 <= arg10.length - 1) {
v5[v3] = ((byte)(v2 | (arg10[v0 + v3] & 255) >>> v3 * 2 + 2));
v2 = ((byte)(((arg10[v0 + v3] & 255) << (2 - v3) * 2 + 2 & 255) >>> 2));
}
else {
v5[v3] = v2;
v2 = 64;
}
++v3;
}
v5[v8] = v2;
int v2_1;
for(v2_1 = 0; v2_1 <= v8; ++v2_1) {
if(v5[v2_1] <= 63) {
v4.append(a[v5[v2_1]]);
}
else {
v4.append('=');
}
}
}
return v4.toString();
}
}
可以看到对 listone
的加密结果为 bSt9kSRzQ3==
。
然后我又找了一个python实现的对变种base64的加密实现。
base64_charset = ['i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N']
def encode(origin_bytes):
# 将每一位bytes转换为二进制字符串
base64_bytes = ['{:0>8}'.format(str(bin(b)).replace('0b', '')) for b in origin_bytes]
resp = ''
nums = len(base64_bytes) // 3
remain = len(base64_bytes) % 3
integral_part = base64_bytes[0:3 * nums]
while integral_part:
# 取三个字节,以每6比特,转换为4个整数
tmp_unit = ''.join(integral_part[0:3])
tmp_unit = [int(tmp_unit[x: x + 6], 2) for x in [0, 6, 12, 18]]
# 取对应base64字符
resp += ''.join([base64_charset[i] for i in tmp_unit])
integral_part = integral_part[3:]
if remain:
# 补齐三个字节,每个字节补充 0000 0000
remain_part = ''.join(base64_bytes[3 * nums:]) + (3 - remain) * '0' * 8
# 取三个字节,以每6比特,转换为4个整数
# 剩余1字节可构造2个base64字符,补充==;剩余2字节可构造3个base64字符,补充=
tmp_unit = [int(remain_part[x: x + 6], 2) for x in [0, 6, 12, 18]][:remain + 1]
resp += ''.join([base64_charset[i] for i in tmp_unit]) + (3 - remain) * '='
return resp
if __name__ == '__main__':
s = 'listone'.encode()
local_base64 = encode(s)
print('使用本地base64加密:', local_base64)
然后我们对 listone
进行加密
可以看到结果与java运行的一致,也因此证明了我们的猜想。
通过解压软件解压apk文件,然后可以在lib目录里找到这个so文件。
接下来就是看 so
文件里的 ncheck
函数是干什么的了。
这个v6我们可以大胆地猜测它为我们传入的字符串
然后我们看那个循环干了什么
v7 = 0;
do
{
//将s1[v7]的地址赋值给v8
v8 = &s1[v7];
//第一轮就是 s1[0] = v6[16]
s1[v7] = v6[v7 + 16];
//第一轮就是先 v9 = v6[0],然后v7 = v7+1 = 1
v9 = v6[v7++];
//第一轮是v8[16]=s1[16]=v9
v8[16] = v9;
}
while ( v7 != 16 );
分析之后就可以知道,这个循环就是将我们传入的字符串的前16位与后16位字符对调。
然后继续分析下一个循环
v10 = 0;
do
{
v12 = __OFSUB__(v10, 30);
v11 = v10 - 30 < 0;
//第一轮v16=s1[0]
v16 = s1[v10];
//第一轮s1[0] = s1[1]
s1[v10] = s1[v10 + 1];
//第一轮s1[1] = v16 = s1[0]
s1[v10 + 1] = v16;
v10 += 2;
}
while ( v11 ^ v12 );
可以发现这个循环是将字符串两两对调位置。
现在再来分析循环的结束条件,这个结束条件一开始直接把我整懵逼了,查了些资料,__OFSUB__
表示x-y是否溢出,溢出返回1,没有溢出返回0。在此情况下,应该不存在溢出,则返回均为0,即v12=0,当v10>30时,v11=0。此时异或得0,循环结束。
这里返回值是与一个字符串比较的结果,那我们就将该字符串逆推回去,写出逆向脚本。
写出脚本为:
def decode(base64_str):
base64_charset = "i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN"
base64_bytes = ['{:0>6}'.format(str(bin(base64_charset.index(s))).replace('0b', '')) for s in base64_str if
s != '=']
resp = bytearray()
nums = len(base64_bytes) // 4
remain = len(base64_bytes) % 4
integral_part = base64_bytes[0:4 * nums]
while integral_part:
tmp_unit = ''.join(integral_part[0:4])
tmp_unit = [int(tmp_unit[x: x + 8], 2) for x in [0, 8, 16]]
for i in tmp_unit:
resp.append(i)
integral_part = integral_part[4:]
if remain:
remain_part = ''.join(base64_bytes[nums * 4:])
tmp_unit = [int(remain_part[i * 8:(i + 1) * 8], 2) for i in range(remain - 1)]
for i in tmp_unit:
resp.append(i)
return resp
if __name__ == '__main__':
flag_1 = 'MbT3sQgX039i3g==AQOoMQFPskB1Bsc7'
print('加密后的字符串:',flag_1)
# 前后16位调换位置
flag_2 = flag_1[len(flag_1)//2:] + flag_1[0:len(flag_1)//2]
print('前后16位调换位置:',flag_2)
flag_3 = ''
for i in range(len(flag_2)//2):
flag_3 += flag_2[i*2+1] + flag_2[i*2]
print('前后两位调换位置:',flag_3)
print('解密后:',decode(flag_3))
最后运行脚本:
就解出flag的值了。
这个题总体来说逻辑比上一个easyjava要简单一些,不过这个涉及到了对so文件的简单分析,也算是学到了一个小知识吧。