吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 20511|回复: 259
上一主题 下一主题
收起左侧

[Android 原创] 某打卡软件的定位地址修改,实现任何地点打卡

    [复制链接]
跳转到指定楼层
楼主
bravin 发表于 2022-11-9 11:53 回帖奖励
本内容相对初级,包含
1,逻辑代码定位
2,反资源混淆
3,smali生成和修改

一 反资源混淆

公司的打卡软件,某力,并不是钉钉

进入首页后有当前时间和当前的位置信息

首先把apk包拖到jadx里发现代码和资源都被混淆了


使用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里找到属性对应的值

然后在public里找到属性对应的混淆后的名字fd


写个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("<public")) {
                    String type = null;
                    String name = null;
                    String id = null;
                    m = p.matcher(line);

                    while (m.find()) {
                        if (type == null) {
                            type = fix(m.group());
                        } else if (name == null) {
                            name = fix(m.group());
                        } else if (id == null) {
                            id = fix(m.group());
                        }
                    }
                    if (publicIdNameMap.containsKey(id)) {
                        throw new IllegalArgumentException("出错!");
                    }

                    publicIdNameMap.put(id, name);

//                    System.out.println("type: " + type + " name:" + name + " id:" + id);
                }
            }

            bufReader.close();

            inReader.close();

            in.close();

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("读取" + filename + "出错!");
        }
    }

    private static Map<String, String> publicIdNameMap = new HashMap<>();
    private static Map<String, String> attrNameIdMap = new HashMap<>();

    public static void handle() {
        try {
            List<File> 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


当app在定位页的时候,在cmd命令行里使用adb shell dumpsys activity top > D:\apktool\activityTop.txt命令拉取顶部activity信息

然后在文件中搜索刚才获得的pid号,能够直接定位到目标app的activity名称和当前activity的View结构



在代码里查看activity的java代码,能够找到页面的布局文件名称R.layout.activity_smart_office_access

在jadx里查看布局文件,发现布局文件很简单,只是一个容器而已

在activity里能够看到5个fragment变量,从名称上可以猜测是和首页的5个tab对应起来的,所以定位的逻辑大概率在第一个fragment里,既HomePageFragment里

在jadx里查看lib里的so文件,凭经验可以知道app用的是高德地图sdk,直接在HomePageFragment暴力搜索Location,发现了高德地图的定位回调

当时冒出来一个思路,在百度地图开放平台里查到公司的经纬度,回调里的经纬度改成公司的经纬度是不是就可以了,先按照这个思路操作
找到HomePageFragment文件的smali文件,定位到方法,把经纬度改掉

打包签名安装,发现首页提示无法定位,动态调试smali,发现代码是执行了,经纬度确实被修改了,但是为啥还是不行呢,猜测可能有其它地方处理了定位逻辑,另外首页是展示了位置地址的,但是高德地图回调方法里只是对经纬度做了处理,并没有涉及到地址的处理,所以这也能印证,肯定有其它地方处理地址,那就继续找吧
回到HomePageFragment代码,然后可以发现HomePageFragment里嵌套着HomeWebViewContainerFragment,而HomeWebViewContainerFragment里逻辑简单,嵌套着SDKWebViewFragment,到这里能够发现,打卡的业务逻辑是放在WebView里的,但是webView的定位还是要通过jsBridge调用app的原生方法处理的。SDKWebViewFragment里有大量业务代码,直接搜索Location,能够找到一个方法
[Java] 纯文本查看 复制代码
[url=home.php?mod=space&uid=1892347]@Override[/url] // 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() <= com.delicloud.app.access.ibeacon.b.Vh) {
                    LocationGetResult locationGetResult = new LocationGetResult();
                    locationGetResult.setData(new LocationGetResult.LocationGetData(addressModel.getLatitude(), addressModel.getLongitude(), addressModel.getName(), addressModel.getAddress()));
                    Log.e("cache", com.delicloud.app.http.utils.c.ai(locationGetResult));
                    dm.a.a(this.mContentActivity, com.delicloud.app.commom.b.abt, null);
                    return locationGetResult;
                }
                final boolean[] zArr = {true};
                ab.create(new ae<Long>() { // from class: com.delicloud.app.jsbridge.ui.fragment.SDKWebViewFragment.6
                    @Override // jf.ae
                    public void a(ad<Long> 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<Long>() { // 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;-><init>()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;-><init>()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的困难度

免费评分

参与人数 76威望 +1 吾爱币 +86 热心值 +70 收起 理由
lewisn + 1 + 1 谢谢@Thanks!
edge + 1 + 1 谢谢@Thanks!
debug_cat + 2 + 1 现在最新版本2.5.9重签名后会提示被篡改
lqlp0153 + 1 + 1 谢谢@Thanks!
yestady + 1 谢谢@Thanks!
qtfreet00 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zzm8817 + 1 + 1 谢谢@Thanks!
wangzhi0013 + 1 + 1 写的很好,学到了
guoyuhui + 1 + 1 我很赞同!
caidesi520 + 1 + 1 用心讨论,共获提升!
ag129 + 1 谢谢@Thanks!
dsxu + 1 用心讨论,共获提升!
asiaboy1984 + 1 + 1 用心讨论,共获提升!
wenye + 1 我很赞同!
pdcba + 1 + 1 谢谢@Thanks!
NoooomoRe + 1 + 1 谢谢@Thanks!
xylqr + 1 + 1 谢谢@Thanks!
haoya + 1 + 1 热心回复!
liyiwen0403 + 1 + 1 用心讨论,共获提升!
lookerJ + 1 + 1 谢谢@Thanks!
mosquito1 + 1 摸鱼是进步动力!
3475dadada + 1 + 1 热心回复!
lyslxx + 1 + 1 我很赞同!
aizijun + 1 + 1 谢谢@Thanks!
zuyao128 + 1 + 1 用心讨论,共获提升!
UIor8QsgR2 + 1 + 1 我很赞同!
Dreamer_952 + 1 用心讨论,共获提升!
zkaizhige + 1 我很赞同!
cooker689 + 1 热心回复!
shuaibiliao + 1 + 1 热心回复!
fffffteam + 1 谢谢@Thanks!
xnink + 1 谢谢@Thanks!
18920706637 + 1 谢谢@Thanks!
Eric9527 + 1 + 1 用心讨论,共获提升!
m287095 + 1 感谢分享,学习了
3118228010 + 1 + 1 用心讨论,共获提升!
18316979508 + 1 + 1 我很赞同!
chakid125 + 1 我很赞同!
zyk995217 + 1 我很赞同!
b614771877 + 1 + 1 我很赞同!
13607635187 + 1 + 1 用心讨论,共获提升!
bscynl + 1 谢谢@Thanks!
pollex + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
kenanxwl + 1 + 1 用心讨论,共获提升!
丨点儿 + 1 热心回复!
zzy17468 + 1 + 1 谢谢@Thanks!
niceboge + 1 + 1 求一份,非常感谢,太厉害了。
xingkongyongye + 1 + 1 用心讨论,共获提升!
July123 + 1 + 1 热心回复!
MatteoGz97 + 1 + 1 用心讨论,共获提升!
wang12345678 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
smail0829 + 1 我很赞同!
ToTVoV + 1 我很赞同!
iphone7 + 1 我很赞同!
cf917 + 1 + 1 我很赞同!
RKKR + 1 + 1 我很赞同!
canleng + 1 + 1 用心讨论,共获提升!
bot123 + 1 + 1 热心回复!
coderWhen + 1 + 1 谢谢@Thanks!
唯一11044 + 1 + 1 我很赞同!
mte456789 + 1 + 1 我很赞同!
zyh109 + 1 + 1 我很赞同!
shalj + 1 + 1 热心回复!
h07799486 + 1 + 1 用心讨论,共获提升!
Ajin1989 + 1 + 1 我很赞同!
skiss + 1 + 1 谢谢@Thanks!
NMG69 + 1 + 1 热心回复!
xyl52p + 1 谢谢@Thanks!
renshaowei + 1 + 1 谢谢@Thanks!
那些年打的飞机 + 1 + 1 谢谢@Thanks!
林伊轩 + 2 + 1 很厉害的样子
raycerlane + 1 我很赞同!
诗和远方代言人 + 1 + 1 小机灵鬼,摸鱼多久了
WXNICE + 1 + 1 谢谢@Thanks!
一弍彡亖乄 + 1 + 1 我很赞同!
GenW + 5 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| bravin 发表于 2022-11-9 14:33 |楼主
lye123456 发表于 2022-11-9 13:56
修改过的app打卡后,公司后台不知道会不会发现的??

这个就不知道了哈
推荐
hacker922 发表于 2022-11-13 20:49
keview 发表于 2022-11-9 14:06
钉钉可用吗,后台会不会报使用了虚拟定位

以前的这种类似修改 钉钉后台是会报的 钉钉用的量太大了,不要想了 就算有,爆出来估计也很快死
沙发
24unicorns 发表于 2022-11-9 13:00
3#
jone33 发表于 2022-11-9 13:02
虽然有用,但不建议啊
4#
刘留留 发表于 2022-11-9 13:34
太厉害了,感谢分享,学习了
5#
lye123456 发表于 2022-11-9 13:56
修改过的app打卡后,公司后台不知道会不会发现的??
6#
童年低调 发表于 2022-11-9 13:58
厉害了我个哥
7#
smnra 发表于 2022-11-9 14:03
来了来了   ,前排,  厉害
8#
keview 发表于 2022-11-9 14:06
钉钉可用吗,后台会不会报使用了虚拟定位
9#
moke945 发表于 2022-11-9 14:06
海尔的打卡软件怎么定位呀?
10#
hncs2008 发表于 2022-11-9 14:06
前排支持
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-23 18:17

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表