本帖最后由 Botton 于 2024-7-28 10:35 编辑
0x01、前言
最近在学安卓逆向,正好把春节的Android中级题给做一做
0x02、过程
先把apk丢到GDA里面看一下
包名:com.zj.wuaipojie2024_2 入口:com.zj.wuaipojie2024_2.MainActivity
然后用jadx看下MainActivity下面有什么东西,发现有个叫checkPassword的方法
用frIDA来hook看一下
[JavaScript] 纯文本查看 复制代码 Java.perform(function () {
let MainActivity = Java.use("com.zj.wuaipojie2024_2.MainActivity");
MainActivity["checkPassword"].implementation = function (str) {
let ret = this["checkPassword"](str);
console.log(str,ret);
return ret;
};
});
随便滑一个密码,可以发现方法被调用了,手势的索引是
0 1 2
3 4 5
6 7 8
传入的str就是我们的密码
那么看一下checkPassword里面进行了哪些操作
checkPassword方法先是从apk的assets目录下拿到classes.dex,复制了一份到app内部空间的data目录下,命名为1.dex,然后加载这个1.dex,调用com.zj.wuaipojie2024_2.C类下的isValidate方法
这里看一下app的运行日志,adb logcat | grep com.zj.wuaipojie2024_2.MainActivity
这里用cmder来执行命令,因为grep是linux下的命令
可以发现有这么一条日志
Suppressed: java.io.IOException: Failed to open dex files from /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex because: Failure to verify dex file '/data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex': Bad checksum (c607ea12, expected 22dcea4c)
查一下关于dex的资料 https://source.android.com/docs/core/runtime/dex-format?hl=zh-cn
我们把这个1.dex下载来用010 Editor打开看看,模板选dex
可以看到checksum和signature是红色的,也就是因为checksum的值错误让app以为文件损坏导致1.dex没有成功加载
写一份脚本修复一下
[Python] 纯文本查看 复制代码 import hashlib,zlib
def repairChecksum(dex_file):
self = open(dex_file, "rb+")
self.seek(8)
# checksum是uint32,小端存储
sourceData = self.read(4)[::-1]
self.seek(12)
checkdata = self.read()
checksum = format(zlib.adler32(checkdata), 'x')
print ("头部原checksum:",sourceData.hex())
print ("计算checksum:",checksum)
if sourceData.hex() == checksum:
print('checksum校验正常')
else:
self.seek(8)
# 小端写入
self.write(bytes.fromhex(checksum)[::-1])
print('checksum效验异常,修复成功')
def repairSignature(dex_file):
self = open(dex_file, "rb+")
self.seek(12)
sourceData = self.read(20)
self.seek(32)
sigdata = self.read()
sha1 = hashlib.sha1()
sha1.update(sigdata)
sha0 = sha1.hexdigest()
print ("计算signature:",sha0)
print ("现在signature:",sourceData.hex())
if sourceData.hex() == sha0:
print('SHA1效验正常')
else:
self.seek(12)
self.write(bytes.fromhex(sha0))
print('SHA1效验异常,修复成功')
if __name__ == '__main__':
repairSignature('1.dex')
repairChecksum('1.dex')
查一下app的安装路径 adb shell pm path com.zj.wuaipojie2024_2
package:/data/app/com.zj.wuaipojie2024_2-KFnHRFDetvCIbKXjR_4LTQ==/base.apk
这里用mt管理器把修复后的1.dex替换到base.apk中assets目录下的classes.dex
因为调用checkPassword方法执行时会从这拿原来的classes.dex,复制一份到app内部空间的data目录下,不能直接替换data目录下的1.dex,不然又被没修复的classes.dex覆盖掉了
然后我们重新运行app看一下日志
可以发现日志不一样了,没有之前那个加载dex的报错
分析一下com.zj.wuaipojie2024_2这个类下的执行过程
isValidate方法中调用了getStaticMethod方法,getStaticMethod方法中调用了read方法和fix方法
getStaticMethod中先用read方法中读入app运行目录data下的decode.dex,传入到fix方法后生成了2.dex,然后加载2.dex中com.zj.wuaipojie2024_2.A类下的d方法,最后再将2.dex删除
将修复后的1.dex复制一份到data目录下,命名为decode.dex
记得给文件权限
现在要想办法拿到2.dex 这里我通过frida来hook,阻止2.dex被删除
[JavaScript] 纯文本查看 复制代码 Java.perform(function () {
var deleteFile = Java.use("java.io.File").delete;
deleteFile.implementation = function () {
console.log("delete file: " + this);
return
};
});
把2.dex丢到jadx里看一下
可以看到d方法已经正常了,转换成python代码执行得到密码为048531267
[Python] 纯文本查看 复制代码 def calculate_str():
stringBuffer = []
i = 0
fixed_string = "0485312670fb07047ebd2f19b91e1c5f"
while len(stringBuffer) < 9 and i < 40:
substring = fixed_string[i:i + 1]
if substring not in stringBuffer:
stringBuffer.append(substring)
i += 1
computed_str = "".join(stringBuffer).upper()
print(computed_str)
可以得到提示在B.d下
但是这个B.d显然还有问题,需要再修复
看一下fix方法需要的参数,是从checkPassword方法传过来的
String str2 = (String) new DexClassLoader(file.getAbsolutePath(), getDir("dex", 0).getAbsolutePath(), null, getClass().getClassLoader()).loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate", Context.class, String.class, int[.class).invoke(null, this, str, getResources().getIntArray(R.array.A_offset));
getResources().getIntArray(R.array.A_offset) 得到fix方法中的int i, int i2, int i3
jadx中点进去看一下R.array.A_offset
发现还有一个叫R.array.D_offset的,那我们把传入fix方法中的参数改成这个试试
[JavaScript] 纯文本查看 复制代码 Java.perform(function () {
let AppCompatActivity = Java.use("androidx.appcompat.app.AppCompatActivity");
AppCompatActivity["getResources"].implementation = function () {
let resources = this["getResources"]();
resources.getIntArray.implementation = function (resId) {
let ret = this.getIntArray(resId);
console.log(resId,ret);
if(resId == 2130903040){
return this.getIntArray(0x7f030001);
}
return ret;
};
return resources;
};
var deleteFile = Java.use("java.io.File").delete;
deleteFile.implementation = function () {
console.log("delete file: " + this);
return
};
});
把修复后的2.dex下载来丢到jadx中看一下
可以看到B.d已经显示正常
最后把Utils复制下来稍微修改下即可
[Java] 纯文本查看 复制代码 import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
public class Utils {
public static final String SHA1 = "SHA1";
public static byte[] toULEB128(int i) {
int i2 = i >> 28;
if (i2 > 0) {
return new byte[]{(byte) ((i & 127) | 128), (byte) (((i >> 7) & 127) | 128), (byte) (((i >> 14) & 127) | 128), (byte) (((i >> 21) & 127) | 128), (byte) (i2 & 15)};
}
int i3 = i >> 21;
if (i3 > 0) {
return new byte[]{(byte) ((i & 127) | 128), (byte) (((i >> 7) & 127) | 128), (byte) (((i >> 14) & 127) | 128), (byte) (i3 & 127)};
}
int i4 = i >> 14;
if (i4 > 0) {
return new byte[]{(byte) ((i & 127) | 128), (byte) (((i >> 7) & 127) | 128), (byte) (i4 & 127)};
}
int i5 = i >> 7;
return i5 > 0 ? new byte[]{(byte) ((i & 127) | 128), (byte) (i5 & 127)} : new byte[]{(byte) (i & 127)};
}
public static byte[] getSha1(byte[] bArr) {
try {
return MessageDigest.getInstance("SHA").digest(bArr);
} catch (Exception unused) {
return null;
}
}
public static String md5(byte[] bArr) {
try {
String bigInteger = new BigInteger(1, MessageDigest.getInstance("md5").digest(bArr)).toString(16);
for (int i = 0; i < 32 - bigInteger.length(); i++) {
bigInteger = "0" + bigInteger;
}
return bigInteger;
} catch (NoSuchAlgorithmException unused) {
throw new RuntimeException("ops!!");
}
}
public static int checksum(ByteBuffer byteBuffer) {
byteBuffer.position(12);
int capacity = byteBuffer.capacity();
int i = 1;
int i2 = 0;
boolean z = false;
while (byteBuffer.position() < capacity) {
ArrayList arrayList = new ArrayList();
int i3 = 0;
while (true) {
if (i3 < 1024) {
arrayList.add(Integer.valueOf(byteBuffer.get() & 255));
if (byteBuffer.position() == byteBuffer.limit()) {
z = true;
break;
}
i3++;
} else {
break;
}
}
int[] calculateVar = calculateVar(arrayList, i, i2);
int i4 = calculateVar[0];
int i5 = calculateVar[1];
if (z) {
return (i5 << 16) + i4;
}
i2 = i5;
i = i4;
}
return 0;
}
private static int[] calculateVar(ArrayList<Integer> arrayList, int i, int i2) {
int i3 = 0;
while (i3 < arrayList.size()) {
int intValue = (arrayList.get(i3).intValue() + i) % 65521;
i2 = (i2 + intValue) % 65521;
i3++;
i = intValue;
}
return new int[]{i, i2};
}
public static int[] fromULEB128(ByteBuffer byteBuffer) {
int i;
int i2 = byteBuffer.get() & 255;
if (i2 > 127) {
int i3 = byteBuffer.get() & 255;
i2 = (i2 & 127) | ((i3 & 127) << 7);
if (i3 > 127) {
int i4 = byteBuffer.get() & 255;
i2 |= (i4 & 127) << 14;
if (i4 > 127) {
int i5 = byteBuffer.get() & 255;
i2 |= (i5 & 127) << 21;
if (i5 > 127) {
i2 |= (byteBuffer.get() & 255) << 28;
i = 5;
} else {
i = 4;
}
} else {
i = 3;
}
} else {
i = 2;
}
} else {
i = 1;
}
return new int[]{i2, i};
}
}
|