1,逻辑代码定位
2,反资源混淆
3,smali生成和修改
一 反资源混淆
公司的打卡软件,某力,并不是钉钉
首页.png (108.7 KB, 下载次数: 1)
下载附件
2022-11-9 10:10 上传
进入首页后有当前时间和当前的位置信息
首先把apk包拖到jadx里发现代码和资源都被混淆了
jadx目录.png (22.18 KB, 下载次数: 1)
下载附件
2022-11-9 10:24 上传
使用apktool重新打包,发现大量报错,如下
W: D:\apktool\deliwithres\res\layout\a0.xml:6: error: No resource identifier found for attribute 'lottie_autoPlay' in package 'com.delicloud.app.smartoffice'
W:
W: D:\apktool\deliwithres\res\layout\a0.xml:6: error: No resource identifier found for attribute 'lottie_loop' in package 'com.delicloud.app.smartoffice'
W:
W: D:\apktool\deliwithres\res\layout\a2.xml:5: error: No resource identifier found for attribute 'layout_constraintLeft_toLeftOf' in package 'com.delicloud.app.smartoffice'
W:
W: D:\apktool\deliwithres\res\layout\a2.xml:5: error: No resource identifier found for attribute 'layout_constraintTop_toBottomOf' in package 'com.delicloud.app.smartoffice'
W:
W: D:\apktool\deliwithres\res\layout\a2.xml:6: error: No resource identifier found for attribute 'layout_constraintLeft_toLeftOf' in package 'com.delicloud.app.smartoffice'
W:
W: D:\apktool\deliwithres\res\layout\a2.xml:6: error: No resource identifier found for attribute 'layout_constraintTop_toBottomOf' in package 'com.delicloud.app.smartoffice'
W:
W: D:\apktool\deliwithres\res\layout\a2.xml:7: error: No resource identifier found for attribute 'layout_constraintTop_toBottomOf' in package 'com.delicloud.app.smartoffice'
这个问题的解决可以参考本站另一个帖子
这是因为属性名被混淆了,但是资源名的混淆关系是保存在了public.xml文件中的,并且资源的id值是不变的,所以,我们只需要根据id的值把xml文件中的属性名替换成混淆后的名称就可以了
例如layout_constraintStart_toStartOf这个属性
可以在attr里找到属性对应的值
属性查找.png (146.95 KB, 下载次数: 0)
下载附件
2022-11-9 10:40 上传
然后在public里找到属性对应的混淆后的名字fd
属性.png (16.08 KB, 下载次数: 1)
下载附件
2022-11-9 10:41 上传
写个java代码把资源文件里的别名全部替换掉
[Java] 纯文本查看 复制代码public class Main {
static Random random = new Random();
static Pattern p = Pattern.compile("\"(.*?)\"");
static Pattern p1 = Pattern.compile("D:(.*?)xml");
static Pattern p2 = Pattern.compile("'(.*?)'");
static Matcher m;
static Matcher m1;
static Matcher m2;
public static String fix(String a){
return a.substring(1, a.length() - 1);
}
public static void handleAttr(String filename) {
try {
FileInputStream in = new FileInputStream(filename);
InputStreamReader inReader = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader bufReader = new BufferedReader(inReader);
String line = null;
while ((line = bufReader.readLine()) != null) {
line = line.trim();
if (line.startsWith("public static final int ")) {
line = line.replace("public static final int ", "");
line = line.replace(";", "");
String[] temp = line.split(" = ");
if (attrNameIdMap.containsKey(temp[0])) {
throw new IllegalStateException("重复KEY");
} else {
attrNameIdMap.put(temp[0], temp[1]);
}
}
}
bufReader.close();
inReader.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
System.out.println("读取" + filename + "出错!");
}
}
public static void handleErrorLog(String filename) {
String lastFileName = null;
String fileContent = null;
try {
FileInputStream in = new FileInputStream(filename);
InputStreamReader inReader = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader bufReader = new BufferedReader(inReader);
String line = null;
while ((line = bufReader.readLine()) != null) {
line = line.trim();
if (line.length() > 10) {
// System.out.println(line);
String address = null;
String attribute = null;
String packageName = null;
m1 = p1.matcher(line);
m2 = p2.matcher(line);
while (m1.find()) {
address = m1.group();
}
while (m2.find()) {
if (attribute == null) {
attribute = fix(m2.group());
} else if (packageName == null) {
packageName = fix(m2.group());
}
}
if (!address.equals(lastFileName)) {
if (lastFileName != null) {
// 保存文件
FileIOUtils.writeFileFromString(lastFileName, fileContent, false);
}
fileContent = FileIOUtils.readFile2String(address);
}
lastFileName = address;
// 获取别名
String id = attrNameIdMap.get(attribute);
if (id == null) {
System.out.println("未找到数据" + attribute);
throw new IllegalStateException("未找到数据");
} else {
String newName = publicIdNameMap.get(id);
fileContent = fileContent.replaceAll(":" + attribute + "=", ":" + newName + "=");
System.out.println("attribute: " + attribute + " newName:" + newName + " id:" + id);
}
// System.out.println("address: " + address + " attribute:" + attribute + " packageName:" + packageName);
}
}
FileIOUtils.writeFileFromString(lastFileName, fileContent, false);
bufReader.close();
inReader.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
System.out.println("读取" + filename + "出错!");
}
}
public static void handlePublicXML(String filename) {
try {
FileInputStream in = new FileInputStream(filename);
InputStreamReader inReader = new InputStreamReader(in, "UTF-8");
BufferedReader bufReader = new BufferedReader(inReader);
String line = null;
while ((line = bufReader.readLine()) != null) {
line = line.trim();
// System.out.println("第" + i + "行:" + line);
if (line.startsWith(" publicIdNameMap = new HashMap();
private static Map attrNameIdMap = new HashMap();
public static void handle() {
try {
List files = FileUtils.listFilesInDir("D:\\apktool\\deliwithres\\smali_classes2\\com\\delicloud\\app\\jsbridge\\ui\\fragment");
for (File file : files) {
String content = FileIOUtils.readFile2String(file);
if (content.contains(".method public a(Lcom/delicloud/app/jsbridge/entity/request/LocationGetRequest;Lcom/d")) {
System.out.println(file.getPath());
break;
}
}
}catch (Exception e) {
}
}
public static void main(String[] args) {
// write your code here
handlePublicXML("D:\\apktool\\deliwithres\\res\\values\\public.xml");
handleAttr("D:\\decompiler\\attr.txt");
handleErrorLog("D:\\decompiler\\errorlog.txt");
}
}
再用apktool打包,发现可以成功打包
二 逻辑定位和修改
使用adb shell ps指令找到app的pid
pid.png (2.69 KB, 下载次数: 0)
下载附件
2022-11-9 10:51 上传
当app在定位页的时候,在cmd命令行里使用adb shell dumpsys activity top > D:\apktool\activityTop.txt命令拉取顶部activity信息
然后在文件中搜索刚才获得的pid号,能够直接定位到目标app的activity名称和当前activity的View结构
activity.png (15.65 KB, 下载次数: 1)
下载附件
2022-11-9 10:56 上传
view.png (123.27 KB, 下载次数: 0)
下载附件
2022-11-9 10:56 上传
在代码里查看activity的java代码,能够找到页面的布局文件名称R.layout.activity_smart_office_access
在jadx里查看布局文件,发现布局文件很简单,只是一个容器而已
content.png (26.85 KB, 下载次数: 0)
下载附件
2022-11-9 11:02 上传
在activity里能够看到5个fragment变量,从名称上可以猜测是和首页的5个tab对应起来的,所以定位的逻辑大概率在第一个fragment里,既HomePageFragment里
在jadx里查看lib里的so文件,凭经验可以知道app用的是高德地图sdk,直接在HomePageFragment暴力搜索Location,发现了高德地图的定位回调
location.png (39.64 KB, 下载次数: 1)
下载附件
2022-11-9 11:13 上传
当时冒出来一个思路,在百度地图开放平台里查到公司的经纬度,回调里的经纬度改成公司的经纬度是不是就可以了,先按照这个思路操作
找到HomePageFragment文件的smali文件,定位到方法,把经纬度改掉
jwd.png (18.89 KB, 下载次数: 0)
下载附件
2022-11-9 11:18 上传
打包签名安装,发现首页提示无法定位,动态调试smali,发现代码是执行了,经纬度确实被修改了,但是为啥还是不行呢,猜测可能有其它地方处理了定位逻辑,另外首页是展示了位置地址的,但是高德地图回调方法里只是对经纬度做了处理,并没有涉及到地址的处理,所以这也能印证,肯定有其它地方处理地址,那就继续找吧
回到HomePageFragment代码,然后可以发现HomePageFragment里嵌套着HomeWebViewContainerFragment,而HomeWebViewContainerFragment里逻辑简单,嵌套着SDKWebViewFragment,到这里能够发现,打卡的业务逻辑是放在WebView里的,但是webView的定位还是要通过jsBridge调用app的原生方法处理的。SDKWebViewFragment里有大量业务代码,直接搜索Location,能够找到一个方法
[Java] 纯文本查看 复制代码@Override // com.delicloud.app.jsbridge.b
public BaseSDKResult a(LocationGetRequest locationGetRequest, com.delicloud.app.jsbridge.main.c cVar) {
if (h.cn(this.mContentActivity)) {
if (l.j(this)) {
AddressModel addressModel = (AddressModel) dm.a.am(this.mContentActivity, com.delicloud.app.commom.b.abt);
if (locationGetRequest.isCache() && addressModel != null && System.currentTimeMillis() - addressModel.getCache_time() () { // from class: com.delicloud.app.jsbridge.ui.fragment.SDKWebViewFragment.6
@Override // jf.ae
public void a(ad adVar) throws Exception {
if (SDKWebViewFragment.this.alD == null) {
return;
}
SDKWebViewFragment.this.alD.a(new a.InterfaceC0142a() { // from class: com.delicloud.app.jsbridge.ui.fragment.SDKWebViewFragment.6.1
@Override // com.delicloud.app.tools.utils.a.InterfaceC0142a
public void a(double d2, double d3, String str, String str2, String str3, String str4) {
zArr[0] = false;
LocationGetResult locationGetResult2 = new LocationGetResult();
locationGetResult2.setData(new LocationGetResult.LocationGetData(Double.valueOf(d2), Double.valueOf(d3), str, str2));
Log.i(SocializeConstants.KEY_LOCATION, com.delicloud.app.http.utils.c.ai(locationGetResult2));
SDKWebViewFragment.this.a(com.delicloud.app.jsbridge.b.aHj, locationGetResult2);
}
@Override // com.delicloud.app.tools.utils.a.InterfaceC0142a
public void tx() {
zArr[0] = false;
SDKWebViewFragment.this.a(com.delicloud.app.jsbridge.b.aHj, new BaseSDKResult(JsSDKResultCode.GET_LOCATION_RESULT_FAIL));
}
});
}
}).timeout(60L, TimeUnit.SECONDS).subscribeOn(kh.b.adP()).observeOn(jh.a.Zq()).subscribe(new ai() { // from class: com.delicloud.app.jsbridge.ui.fragment.SDKWebViewFragment.5
@Override // jf.ai
public void onComplete() {
}
@Override // jf.ai
public void onSubscribe(jj.c cVar2) {
}
@Override // jf.ai
/* renamed from: d */
public void onNext(Long l2) {
if (zArr[0]) {
SDKWebViewFragment.this.a(com.delicloud.app.jsbridge.b.aHj, new BaseSDKResult(JsSDKResultCode.GET_LOCATION_RESULT_FAIL));
}
}
@Override // jf.ai
public void onError(Throwable th) {
if (zArr[0]) {
SDKWebViewFragment.this.a(com.delicloud.app.jsbridge.b.aHj, new BaseSDKResult(JsSDKResultCode.GET_LOCATION_RESULT_FAIL));
}
}
});
}
return null;
}
com.delicloud.app.uikit.utils.d.a(this.mContentActivity);
return new BaseSDKResult(JsSDKResultCode.IBEACON_NEED_LOCATION_PERMISSION);
}
这个方法里,获取了一个AddressModel对象,然后检查了这个对象的缓存有效性,如果有效就使用这个对象,否则使用jsbridge调用原生方法,并且这个而的AddressModel使用了name和address对象,而app首页是有地址信息的,所以我们猜测这里可能是真正获取地址的方法
找到这个方法后,修改起来就不难了,思路是重写AddressModel类,让类生成时就包含公司地址信息并且不允许修改。
使用Android studio 中新建一个工程,创建AddressModel对象,把set方法中更改属性的代码全部去掉
[Java] 纯文本查看 复制代码import java.io.Serializable;
public class AddressModel implements Serializable {
private String address = "公司的地址";
private long cache_time = System.currentTimeMillis();
private Double latitude = 0D;// 公司的纬度
private Double longitude = 0D;// 公司的精度
private String name = "公司";
public AddressModel(long j2, Double d2, Double d3, String str, String str2) {
}
public AddressModel() {
}
public long getCache_time() {
return this.cache_time;
}
public void setCache_time(long j2) {
}
public Double getLatitude() {
return this.latitude;
}
public void setLatitude(Double d2) {
}
public Double getLongitude() {
return this.longitude;
}
public void setLongitude(Double d2) {
}
public String getName() {
return this.name;
}
public void setName(String str) {
}
public String getAddress() {
return this.address;
}
public void setAddress(String str) {
}
}
使用java2smali插件,把AddressModel类编译成smali文件,使用这个smali文件中的init方法和set get方法替换反编译出来的AddressModel对应的smali文件里的方法
然后再创建一个类
[Java] 纯文本查看 复制代码public class AAA {
void a(){
AddressModel addressModel = new AddressModel();
System.out.println(addressModel.getAddress());
addressModel = null;
}
}
再编译出smali文件,里面的方法如下
.method a()V
.registers 4
.prologue
.line 10
new-instance v0, Lcom/bravin/game/entity/AddressModel;
invoke-direct {v0}, Lcom/bravin/game/entity/AddressModel;->()V
.line 11
.local v0, "addressModel":Lcom/bravin/game/entity/AddressModel;
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v0}, Lcom/bravin/game/entity/AddressModel;->getAddress()Ljava/lang/String;
move-result-object v2
invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 12
const/4 v0, 0x0
.line 13
return-void
.end method
找到反编译SDKWebViewFragment的smali文件中的.method public a(Lcom/delicloud/app/jsbridge/entity/request/LocationGetRequest;Lcom/delicloud/app/jsbridge/main/c;)Lcom/delicloud/app/jsbridge/entity/result/BaseSDKResult;方法
按照AAA类里的方法的逻辑,替换掉原有方法里的AddressModel对象生成的逻辑
.line 2207
iget-object p2, p0, Lcom/delicloud/app/jsbridge/ui/fragment/SDKWebViewFragment;->mContentActivity:Landroidx/appcompat/app/AppCompatActivity;
const-string v1, "key_address_cache"
new-instance p2, Lcom/delicloud/app/comm/entity/tools/AddressModel;
invoke-direct {p2}, Lcom/delicloud/app/comm/entity/tools/AddressModel;->()V
.line 2209
invoke-virtual {p1}, Lcom/delicloud/app/jsbridge/entity/request/LocationGetRequest;->isCache()Z
move-result p1
if-nez p1, :cond_0
注意if-nez p1, :cond_0这一行和原先的判断不一样,原先是if-eqz p1, :cond_0
然后打包签名安装,发现app可以获取到地址了,并且地址信息就是我们在AddressModel里填写的信息,找个离公司远的地方打开app,发现还是可以打卡,说明我们的修改成功了
总结
这个app并没有什么复杂的反逆向手段,适合新人练手,在定位要修改的业务逻辑时,要结合页面上的元素去定位,另外android studio的java2smali插件可以大大减少写smali的困难度