折腾:
【未解决】重新爬取少儿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
的算法,才能继续爬取数据。
参考之前自己的教程:
安卓应用的安全和破解
去找找jadx试试
此处app是6.1.3的版本

先去找到对应apk文件:
少儿xxx apk 6.1.3
少儿xxx app 6.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的算法计算逻辑