折腾:
【未解决】重新爬取少儿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的算法计算逻辑