最新消息:20210917 已从crifan.com换到crifan.org

【已解决】破解安卓应用少儿趣配音的源码以便于找到sign签名和auth_token的算法计算逻辑

源码 crifan 442浏览 0评论
折腾:
【未解决】重新爬取少儿xxx数据并记录部分的分类层级和是否是动画片
和后来的
【未解决】重爬少儿xxx的所有视频
都需要去破解少儿xxx的app,得到源码,才有机会搞清楚调用api
https://childapi30.xxx.com/square/courseNature?sign=711146489691f7ee6df24c445dfefcb6×tamp=1568538032&uid=37285135&auth_token=MTU2OTgyOTUyNrCtxKuCe7rcr92Mcg
即:
https://childapi30.xxx.com/square/courseNature
时所需要的参数
http的get的query parameter的:
sign和auth_token
另外还有:
timestamp和uid
的算法,才能继续爬取数据。
参考之前自己的教程:
安卓应用的安全和破解
https://book.crifan.com/books/android_app_security_crack/website/
去找找jadx试试
此处app是6.1.3的版本
先去找到对应apk文件:
少儿xxx apk 6.1.3
少儿xxx app 6.1.3
2019少儿xxxv6.1.3老旧历史版本安装包官方免费下载_豌豆荚
-》
再去参考自己的帖子
【已解决】Mac中如何查看Android的apk的包名等信息
去查看appid:
➜  28.0.3 ./aapt dump badging ../../../apk/少儿xxx/2_edd68fa0ca4ac6ac2fb2d943999f4ef1.apk
package: name='com.ishowedu.child.peiyin' versionCode='1693' versionName='6.1.3' compileSdkVersion='28' compileSdkVersionCodename='9'
sdkVersion:'16'
targetSdkVersion:'26'
uses-permission: name='android.permission.WRITE_OWNER_DATA'
...
uses-permission: name='com.ishowedu.child.peiyin.permission.JPUSH_MESSAGE'
application-label:'少儿xxx'
...
native-code: 'armeabi-v7a'
是:
com.ishowedu.child.peiyin
然后改名后去用jadx破解
【已解决】用jadx破解出安卓apk少儿xxx得到源码
从jadx导出源码中看看能否搜到api:
/square/courseNature
的相关字段
可以搜到:
courseNature
的,不过没有完整的源码可以看
继续找Mc(
找到:
再去找this.f的含义
public class FZContactPresenter extends FZBasePresenter implements FZContactContract$Presenter, IReadContactsCallBack {
...
    /* access modifiers changed from: private */
    public List<FZContactInfo> f = new ArrayList();
继续深入找代码逻辑
    private void c(List<FZContactInfo> list) {
...
        this.f.addAll(this.e);
        this.c.showList(false);
    }
去添加了元素给f列表
继续深入研究代码背后逻辑
去搜
auth_token
“sign”
目前从
/Users/crifan/dev/dev_tool/android/apk/少儿xxx/v6.1.3/jadx_export_src/sources/com/fz/lib/net/FZNetApiManager.java
中的
okhttp3.Request.Builder的Response
看来最相关的是:
    private void a(Map<String, String> map) {
        StringBuilder sb = new StringBuilder();
        if (map != null) {
            long currentTimeMillis = (System.currentTimeMillis() / 1000) + this.a.a.a();
            StringBuilder sb2 = new StringBuilder();
            sb2.append(currentTimeMillis);
            sb2.append("");
            map.put("timestamp", sb2.toString());
            HashMap hashMap = new HashMap(map);
            hashMap.put("security_key", this.a.a.b());
            ArrayList arrayList = new ArrayList(hashMap.entrySet());
            Collections.sort(arrayList, new Comparator<Entry<String, String>>() {
                /* renamed from: a */
                public int compare(Entry<String, String> entry, Entry<String, String> entry2) {
                    return ((String) entry.getKey()).compareTo((String) entry2.getKey());
                }
            });
            Iterator it = arrayList.iterator();
            while (it.hasNext()) {
                Entry entry = (Entry) it.next();
                sb.append((String) entry.getKey());
                sb.append((String) entry.getValue());
            }
            map.put("sign", FZNetUtils.a(sb.toString()));
        }
    }
然后再去找找其他相关内容
其中的timestamp此处从get请求中已知是这种1568538032
但是security_key的值,需要好好研究看看
很难从jadx导出的,很晦涩的代码中看出原始逻辑。
所以再去:
【已解决】用dex转jar再转java的三步方式导出安卓app少儿xxx的源码
可惜,后续3步得到的java源码,大都不如之前jadx直接1步从apk中得到的源码,至少对于核心部分
v6.1.3/jadx_export_src/sources/com/fz/lib/net/FZNetApiManager.java
还是有代码的,而3步最后得到的代码,都没有FZNetApiManager.java
结合2种方式得到的源码,结合jadx,procyon等不同反编译器得到的源码,去分析
目前可以看出来是
sources/com/fz/childdubbing/provider/AppNetProvider.java
            public HashMap<String, String> getParams() {
                HashMap<String, String> hashMap = new HashMap<>();
                String str = "auth_token";
                String str2 = "uid";
                if (ProviderManager.getInstance().mLoginProvider.isGeusterUser(false)) {
                    String str3 = User.TYPE_GUESTER;
                    hashMap.put(str2, str3);
                    hashMap.put(str, str3);
                } else {
                    StringBuilder sb = new StringBuilder();
                    sb.append(ProviderManager.getInstance().mLoginProvider.getUser().uid);
                    sb.append("");
                    hashMap.put(str2, sb.toString());
                    hashMap.put(str, ProviderManager.getInstance().mLoginProvider.getUser().auth_token);
                }
                return hashMap;
            }
中的
auth_token的值是
ProviderManager.getInstance().mLoginProvider.getUser().auth_token
继续去找值是如何计算的
import com.fz.childdubbing.ProviderManager;
结果ProviderManager出错看不到源码:
去搜mLoginProvider
不过发现jadx直接1步得到的源码,倒是有
v6.1.3/jadx_export_src/sources/com/fz/childdubbing/ProviderManager.java
但是其实啥逻辑都没有,继续找
.auth_token
找到
v6.1.3/jadx_export_src/sources/com/fz/childmodule/login/service/User.java
中的:
    public void setAuth_token(String str) {
        this.auth_token = str;
    }
去继续找找
setAuth_token
v6.1.3/apk_dex_jar_java/jar_to_java/procyon/com.ishowedu.child.peiyin8392664_procyon/com/fz/childmodule/login/service/db/UserDao.java
    public void readEntity(final Cursor cursor, final User user, int n) {
        user.setUid(cursor.getInt(n + 0));
        user.setUc_id(cursor.getInt(n + 1));
...
        user.setSignature(string9);
        final int n11 = n + 13;
        String string10;
        if (cursor.isNull(n11)) {
            string10 = null;
        }
        else {
            string10 = cursor.getString(n11);
        }
...
        user.setAuth_token(string11);
        final int n13 = n + 18;
        String string12;
        if (cursor.isNull(n13)) {
            string12 = null;
        }
        else {
            string12 = cursor.getString(n13);
        }
但是要去找到n
发现是readEntity的参数n
ILoginProvider
找了半天有个
import com.fz.childmodule.login.service.ILoginProvider;
@Keep
public interface ILoginProvider extends IProvider {
    public static final String PROVIDER_PATH = "/login/router/moduleLogin/iloginprovider";

    User getUser();
是空的
v6.1.3/apk_dex_jar_java/jar_to_java/procyon/com.ishowedu.child.peiyin8392664_procyon/com/fz/childmodule/login/ModuleLoginManager.java
import com.fz.childmodule.login.data.bean.RefreshToken;

    public void refreshToken(final RefreshToken refreshToken) {
        final User user = this.getUser();
        try {
            if (!TextUtils.isEmpty((CharSequence)refreshToken.auth_token)) {
                user.auth_token = refreshToken.auth_token;
            }
...
也可以去看看
com.fz.childmodule.login.data.bean.RefreshToken
还是因为自己有足够高的技术敏感度:
才突然有点意识到
是不是此处auth_token是固定的值?如果是,就好了,就不用研究计算逻辑了。
然后发现不登录,则auth_token是0
所以用手机号加短信验证码去登录后,去看看:
https://childapi30.xxx.com/square/courseNature?sign=256033809d32c00fd1bd1d5e4b0a070c
×tamp=1568620590&uid=37285135&auth_token=MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg

参数:
sign    256033809d32c00fd1bd1d5e4b0a070c
timestamp    1568620590
uid    37285135
auth_token    MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg
发现auth_token:
之前是:MTU2OTgyOTUyNrCtxKuCe7rcr92Mcg
现在是:MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg
还是有点不一样的
以为是一样的
再去多试试几次
https://childapi30.xxx.com/square/courseNature?sign=05fd469214bb41be9c364e3d1630368c×tamp=1568620889&uid=37285135&auth_token=MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg

sign    05fd469214bb41be9c364e3d1630368c
timestamp    1568620889
uid    37285135
auth_token    MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg
-》
刚才:MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg
此刻:MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg
-》是和刚才是一样的
-》貌似是:对于单次登录后,这个auth_token是不变的?
杀掉app,重新打开(但无需重新登录),再去试试
https://childapi30.xxx.com/square/courseNature?sign=ad93c1df2d648b1d7f71195304520585×tamp=1568621135&uid=37285135&auth_token=MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg

sign    ad93c1df2d648b1d7f71195304520585
timestamp    1568621135
uid    37285135
auth_token    MTU2OTkxNjU3ObCtxKuCe7rcr92Mcg
-》确定是的:
对于单次登录后,auth_token是一样的
-》所以无需研究auth_token的值的计算逻辑了。
继续去研究sign的计算逻辑。
继续去找FZNetUtils
sources/com/fz/lib/net/utils/FZNetUtils.java
public class FZNetUtils {
    public static String a(String str) {
        if (TextUtils.isEmpty(str)) {
            return "";
        }
        try {
            byte[] digest = MessageDigest.getInstance("MD5").digest(str.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder(digest.length * 2);
            for (byte b : digest) {
                byte b2 = b & 255;
                if (b2 < 16) {
                    sb.append("0");
                }
                sb.append(Integer.toHexString(b2));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Huh, MD5 should be supported?", e);
        } catch (UnsupportedEncodingException e2) {
            throw new RuntimeException("Huh, UTF-8 should be supported?", e2);
        }
    }
}
回头可以好好研究其值
-》好像就是去做MD5的digest?
但是此处需要先去搞清楚
hashMap.put(“security_key”, this.a.a.b());
中的this.a.a.b()是如何得到的
此处比较迷惑的是this.a和this.a.a是什么
不够后来通过搜this.a.a,发现还有调用:
this.a.a.getParams()
所以去找有getParams()的类
找了下,感觉是
sources/com/fz/childdubbing/provider/AppNetProvider.java
    public <T> FZINetConfig<T> createNetConfig(final Class<T> cls, final String str) {
        return new FZINetConfig<T>() {
            public long a() {
                return PreferenceHelper.getInstance().getSerTimeOffset();
            }


            public String b() {
                return "qpy68c681cbdcd102363";
            }


            public String c() {
                return "release.cer";
            }


            public boolean d() {
                return true;
            }


            public Class<T> e() {
                return cls;
            }


            public String getBaseUrl() {
                return str;
            }


            public HashMap<String, String> getHeaders() {
                HashMap<String, String> hashMap = new HashMap<>();
                hashMap.put("App-Version", FZUtils.b((Context) DubbingApplication.getApplication()));
                hashMap.put("User-Agent", "android");
                StringBuilder sb = new StringBuilder();
                sb.append(FZUtils.a((Context) DubbingApplication.getApplication()));
                String str = "";
                sb.append(str);
                hashMap.put("versionCode", sb.toString());
                hashMap.put("Client-OS", AppNetProvider.headerFormat(VERSION.RELEASE));
                hashMap.put("Device-Model", AppNetProvider.headerFormat(Build.MODEL));
                hashMap.put("Umeng-Channel", FZUtils.a((Context) DubbingApplication.getApplication(), str));
                String str2 = "unknow";
                String str3 = "idfa";
                if (VERSION.SDK_INT >= 23 && DubbingApplication.getApplication().checkSelfPermission("android.permission.READ_PHONE_STATE") == 0) {
                    String c2 = FZUtils.c((Context) DubbingApplication.getApplication());
                    if (c2 == null) {
                        c2 = str2;
                    }
                    hashMap.put(str3, c2);
                } else if (VERSION.SDK_INT < 23) {
                    String c3 = FZUtils.c((Context) DubbingApplication.getApplication());
                    if (c3 == null) {
                        c3 = str2;
                    }
                    hashMap.put(str3, c3);
                }
                hashMap.put("DISTINCT-ID", AppTrackProvider.getDistinctId());
                if (ProviderManager.getInstance().mIPlatformProvider != null) {
                    hashMap.put("AREA", ProviderManager.getInstance().mIPlatformProvider.getArea());
                }
                return hashMap;
            }


            public HashMap<String, String> getParams() {
                HashMap<String, String> hashMap = new HashMap<>();
                String str = "auth_token";
                String str2 = "uid";
                if (ProviderManager.getInstance().mLoginProvider.isGeusterUser(false)) {
                    String str3 = "0";
                    hashMap.put(str2, str3);
                    hashMap.put(str, str3);
                } else {
                    StringBuilder sb = new StringBuilder();
                    sb.append(ProviderManager.getInstance().mLoginProvider.getUser().uid);
                    sb.append("");
                    hashMap.put(str2, sb.toString());
                    hashMap.put(str, ProviderManager.getInstance().mLoginProvider.getUser().auth_token);
                }
                return hashMap;
            }
        };
    }


    public String getBaseUrl() {
        return "https://childapi30.xxx.com";
    }
即FZINetConfig的b()的”qpy68c681cbdcd102363″
因为
另外的a()的是:PreferenceHelper.getInstance().getSerTimeOffset()
以及getParams()是net,http,网络相关的,返回的auth_token和uid的hash的map
而回到:
sources/com/fz/lib/net/FZNetApiManager.java
再去看
    class FZParamsInterceptor implements Interceptor {
        public FZParamsInterceptor() {
        }

        public Response intercept(Chain chain) throws IOException {
            return FZNetApiManager.this.a(chain, FZNetApiManager.this.a.a.getParams());
        }
    }
就说得通了:
此处是给http的net的配置中,加了个拦截器Interceptor
对每个请求都加上了FZNetApiManager.this.a.a.getParams()
即:带auth_token和uid的hash的map
所以现在只剩:
FZNetUtils.java
中的计算逻辑了。
但是具体不是很熟悉,所以只能去尝试换成python代码,变调试和反推实际代码逻辑了:
【已解决】用Python代码实现少儿xxx的请求参数sign的计算逻辑
至此已解决核心问题,如何计算sign值和auth_token值。

转载请注明:在路上 » 【已解决】破解安卓应用少儿趣配音的源码以便于找到sign签名和auth_token的算法计算逻辑

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
90 queries in 0.192 seconds, using 23.81MB memory