最近在学安卓逆向,正好把春节的Android中级题给做一做
0x02、过程
先把apk丢到GDA里面看一下
包名:com.zj.wuaipojie2024_2 入口:com.zj.wuaipojie2024_2.MainActivity
1.jpg (99.66 KB, 下载次数: 0)
下载附件
2024-7-28 07:17 上传
然后用jadx看下MainActivity下面有什么东西,发现有个叫checkPassword的方法
2.png (142.89 KB, 下载次数: 0)
下载附件
2024-7-28 07:23 上传
用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就是我们的密码
3.jpg (74.19 KB, 下载次数: 0)
下载附件
2024-7-28 07:30 上传
那么看一下
[color=]checkPassword
里
面进行了哪些操作
checkPassword方法先是从apk的assets目录下拿到classes.dex,复制了一份到app内部空间的data目录下,命名为1.dex,然后加载这个1.dex,调用com.zj.wuaipojie2024_2.C类下的isValidate方法
这里看一下app的运行日志,
adb logcat | grep
[color=]com.zj.wuaipojie2024_2.MainActivity
这里用cmder来执行命令,因为
grep
是linux下的命令
4.png (341.05 KB, 下载次数: 0)
下载附件
2024-7-28 07:51 上传
可以发现有这么一条日志
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
5.png (154.85 KB, 下载次数: 0)
下载附件
2024-7-28 08:04 上传
可以看到
[color=]checksum
和
[color=]signature
是红色的,也就是因为
[color=]checksum
的值错误让app以为文件损坏导致
[color=]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的安装路径
[color=]adb shell pm path com.zj.wuaipojie2024_2
package:/data/app/com.zj.wuaipojie2024_2-KFnHRFDetvCIbKXjR_4LTQ==/base.apk
这里用mt管理器把修复后的1.dex
替换到
[color=]base.apk
中
assets
目录下的
classes.dex
因为调用
checkPassword
方
法执行时会从这拿原来的classes.dex
,
复制一份到app内部空间的data目录下,不能直接替换data
目录下的
1.dex
,不然又被没修复的
classes.dex
覆盖掉了
然后我们重新运行app看一下日志
6.png (243.56 KB, 下载次数: 0)
下载附件
2024-7-28 08:26 上传
可以发现日志不一样了,没有之前那个加载dex的报错
分析一下
[color=]com.zj.wuaipojie2024_2
这个类下的执行过程
7.png (169.68 KB, 下载次数: 0)
下载附件
2024-7-28 09:12 上传
[color=]isValidate
方法中调用了
[color=]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
8.jpg (132.55 KB, 下载次数: 0)
下载附件
2024-7-28 09:27 上传
记得给文件权限
现在要想办法拿到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里看一下
9.png (123.98 KB, 下载次数: 0)
下载附件
2024-7-28 09:34 上传
可以看到d
方法已经正常了,转换成python代码执行得到密码为
[color=]048531267
[Python] 纯文本查看 复制代码def calculate_str():
stringBuffer = []
i = 0
fixed_string = "0485312670fb07047ebd2f19b91e1c5f"
while len(stringBuffer)
10.jpg (85.71 KB, 下载次数: 0)
下载附件
2024-7-28 09:38 上传
可以得到提示在
[color=]B.d
下
11.jpg (86.32 KB, 下载次数: 0)
下载附件
2024-7-28 09:39 上传
但是这个B.d
显然还有问题,需要再修复
看一下
fix
方法需要的参数,是从
[color=]checkPassword
方法传过来的
[color=]String
str2
=
[color=](
[color=]String
[color=])
new
DexClassLoader
[color=](
file
.
getAbsolutePath
[color=](
[color=])
,
getDir
[color=](
[color=]"dex"
,
[color=]0
[color=])
.
getAbsolutePath
[color=](
[color=])
,
null
,
getClass
[color=](
[color=])
.
getClassLoader
[color=](
[color=])
[color=])
.
loadClass
[color=](
[color=]"com.zj.wuaipojie2024_2.C"
[color=])
.
getDeclaredMethod
[color=](
[color=]"isValidate"
,
Context
.
class
,
[color=]String
.
class
,
int
[color=][
.
class
[color=])
.
invoke
[color=](
null
,
this
,
str
,
getResources
[color=](
[color=])
.
getIntArray
[color=](
R
.
array
.
A_offset
[color=])
[color=])
;
getResources
[color=](
[color=])
[color=].
[color=]getIntArray
[color=](
[color=]R
[color=].
[color=]array
[color=].
[color=]A_offset
[color=])
得到
[color=]fix
方法中的
int i, int i2, int i3
jadx中点进去看一下
[color=]R.array.A_offset
12.png (170.62 KB, 下载次数: 0)
下载附件
2024-7-28 09:53 上传
发现还有一个叫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
};
});
把修复后的
[color=]2.dex
下载来丢到jadx中看一下
13.png (70.47 KB, 下载次数: 0)
下载附件
2024-7-28 10:00 上传
可以看到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 127) {
int i3 = byteBuffer.get() & 255;
i2 = (i2 & 127) | ((i3 & 127) 127) {
int i4 = byteBuffer.get() & 255;
i2 |= (i4 & 127) 127) {
int i5 = byteBuffer.get() & 255;
i2 |= (i5 & 127) 127) {
i2 |= (byteBuffer.get() & 255)
14.png (95.14 KB, 下载次数: 0)
下载附件
2024-7-28 10:03 上传