分析的APP样本如下,包含APK和抓到的流量
链接:https://pan.baidu.com/s/1f9cyEd6zFqVDPP4YTChLZA
提取码:v14r
--来自百度网盘超级会员V2的分享
后面分析要用到的工具:
Android studio(需要安装好模拟器)
WINRAR压缩包(提取apk文件中的.dex文件)
apktool(用WINRAR提取APK中的androidmanifest.xml文件时可能会导致乱码,所以要用它来提取)
d2j-dex2jar(将.dex文件转为.jar文件,后面会提到)
jd.gui(将.jar文件打开展示成java源代码,后面会提到)
Wireshark(用于分析流量)
APP开发遵循逻辑和视图分离的思想:
我们创建一个activity,android studio会自动生成其对应的xml文件。
注意,任何的activity都要在AndroidManifest.xml中定义。(一般androidstudio会自动完成)
视图在xml中定义:可以直接可视化移动一个按钮进视图,也可以用代码编写。每个元素都会有一个id留给activity去调用。比如按钮A对应一个id,按钮B对应一个Id.
在xml中:
@id/id_name表示引用这个id。
@+id/button1 表示定义一个id。
逻辑在activity中定义:activty要加载上面定义的视图,即布局,要调用setContentView(布局文件的id)。(项目添加的任何资源都会在R文件中生成一个资源id,这里布局文件的id即为对应xml文件的id)
如果要对布局进行一些操作,也是在activity中定义。比如说监听按钮的点击事件,在Java中要使用findviewByID()方法获取布局文件中定义的元素,然后再定义该元素的函数的内容,比如按钮元素的话就可以定义其setonclicklistener函数。而Kotlin不需要使用findviewbyID(),直接使用元素的名字就可以调用该元素了。
在activity中按钮的listen函数中定义
即可实现跳转页面
不指定跳转到哪个activity,而是指定跳转动作和类型,让系统来选择合适的activity.我们可以在AndroidManifest.xml中设置activity的可以相应的动作和类型、相应的协议类型(scheme)。
intent不仅可以打开activity,也可以打开网页。
甚至可以通过intent向下一个或者上一个页面传递数据。
在activity的xml中定义,显示文字。
width和height有三个可选值:
1.match_parent:和父布局大小一样(即和手机屏幕大小一样)
2.wrap_content:恰好包住里面内容。
3.固定值。
还有其他的属性可以选:是否居中,文字颜色,文字大小,。
目标:给一个APK反汇编出java源代码
流程:
1.先用压缩包提取classes.dex文件
2.用dex2jar提取出jar文件
并将这个文件拷至dex2jar工具存放目录下。
打开控制台,使用cd指令进入到dex2jar工具存放的目录下
进入到dex2jar目录下后,输入“d2j-dex2jar.bat classes.dex”指令运行
执行完毕,查看dex2jar目录,会发现生成了classes.dex.dex2jar.jar文件
3.将jar文件导入jd.gui看java源码
上一步中生成的classes.dex.dex2jar.jar文件,可以通过JD-GUI工具直接打开查看jar文件中的代码
res/values/string.xml存了APK的字符串。
同目录下的public.xml有其对应的id。
查找当前目录下包含0x7f10002f的文件
我们用apktool解析出apk的文件夹如下:
(安装apktool前要先安装java1.8.关于apktool如何安装参考https://www.jianshu.com/p/b027856d55ac)
安装完并配置好环境变量后
用以下命令反编译输出到baz目录
从AndroidManifest.xml搜索android.intent.action.MAIN"定位到如下:
所以第一个主页面是com.happy.roulette.activity.SplashActivity
以下便是第一个界面
我们从com.happy.roulette.activity.SplashActivity.class的oncreate函数开始看(因为Oncreate函数是进入到一个新页面后要执行的第一个函数)。
我们继续看checkLocalHost()函数。
每次启动第一个界面都会检查一下host.
loadHostUrl函数会先取出url, 我们继续跟踪loadHostUrl
发现使用了sharepreferences存储这个url在本地。
以下想在本地找到保存这个url的文件。
在模拟器上运行该APP,
打印出APP的package和当前页面的acitivy
以下为APP在主页面时运行命令得到的结果
以下为APP在登录页面时运行命令得到的结果
通过 run-as 命令进入APP的文件目录下
发现无法进入这个APP的目录,因为不是debug版本的APP。
所以后面会通过抓包来获取这个url。
我们继续看checkLocalHost函数,上面我们无法分析出loadHostUrl在本地哪个文件获取url.我们继续看后面的函数调用过程,通过loadHostUrl函数获取到url后,会调用checkAppMaintain来检查这个url。
一直跟踪下去
发现这里会请求这个url
发现会在这个url后拼接/api/checkAppWh.do,然后发送请求。
tcpdump是常用的一个抓包工具,linux或android环境下已经默认安装好。
当用android studio自带的模拟器启动APP后,在电脑终端输入adb shell进入模拟器终端:
进入模拟器终端后用tcpdump命令进行抓包,包保存在/sdcard/capture.pcap
其中
-i是指定网卡为any,
-w表示保存为pacp,
s 0 : tcpdump 默认只会截取前 96 字节的内容,要想截取所有的报文内容,可以使用 -s number, number 就是你要截取的报文字节数,如果是 0 的话,表示截取报文全部内容。
-p : 不让网络接口进入混杂模式。默认情况下使用 tcpdump 抓包时,会让网络接口进入混杂模式。一般计算机网卡都工作在非混杂模式下,此时网卡只接受来自网络端口的目的地址指向自己的数据。当网卡工作在混杂模式下时,网卡将来自接口的所有数据都捕获并交给相应的驱动程序。如果设备接入的交换机开启了混杂模式,使用 -p 选项可以有效地过滤噪声。
抓包结束后按Cltr+C中断后即可以保存文件。
我们在PC终端中把模拟器的抓取的包拿回来本地D盘。在本地终端执行
通过wireshark分析如下:
首先会进行DNS请求,获取这个域名对应的IP:
发现请求了9h.app00app.com这个域名,且IP为204.11.56.48。
checkmaintain执行完后会跳到下面这个回调函数里,
即请求服务器后会回到以下函数。
发现请求这个IP,连接不上。所以会执行上面Onerror函数去获取另外一个域名。
Onerror函数会调用gethost()
继续跟踪
看getNextServerUrl,它会将”https://9h.“拼接域名list中一个域名。
跟踪SERVER_URL_LIST,发现这是一个域名的list,包含以下域名。猜测这种读博网站域名经常被封,所以要多准备几个域名。
获取到域名之后,又会继续去执行checkAppMaintain这个函数去检测域名,即重复上面步骤,直到找到一个可以连接的域名,然后会进入上面的Onresult函数的case48。
通过抓包分析,这里请求了上面域名list中的第二个域名app66app.com
然后与得到的IP地址进行TCP连接,以下为三次握手和密钥协商过程。
为了学习TLS协议,下面我们分析一下协议的过程。
可以看到是TLS1.3协议,先看看Client hello这条消息
我们可以看到Transport layer Security就是传输层安全(TLS),
TLS1.3总共有两层,分别是握手协议(handshake protocol)和记录协议(record protocol),握手协议在记录协议的上层,记录协议是一个分层协议。其中握手协议中还包括了警告协议(alert protocol)。
(图来自https://blog.csdn.net/SkyChaserYu/article/details/104716229#t3,以下部分转自该博客)
接下来看一下Handshake protocol:Hello中的内容:
Handshake Type:ClientHello,表示握手消息类型,此处是ClientHello
Length:508,即长度为508
Version:TLS1.2(0x0303),表示版本号为1.2,TLS1.3中规定此处必须置为0x0303,即TLS1.2,起到向后兼容的作用。1.3版本用来协商版本号的部分在扩展当中,而之前的版本就在此处进行。
Random,随机数,是由安全随机数生成器生成的32个字节。
Session ID Length:会话ID的长度。
Session ID,会话ID,TLS 1.3之前的版本支持“会话恢复”功能,该功能已与1.3版本中的预共享密钥合并。为了兼容以前的版本,该字段必须是非空的,因此不提供TLS 1.3之前会话的客户端必须生成一个新的32字节值。该值不必是随机的,但应该是不可预测的,以避免实现固定在特定值,否则,必须将其设置为空。
Cipher Suites Length,即下面Cipher Suites的长度
Cipher Suites是密码套件,表示客户端提供可选择的加密方式,如图所示:
每个加密套件都包含,密钥交换,签名算法,加密算法,哈希算法。
Compression Methons (1 method)表示压缩方法,长度为1,内容为空
Exentisons扩展部分,是TLS1.3才开始使用,是TLS1.3的显著特征。每一个扩展都包含类型(type),长度(length)和数据(data)三个部分。
下面分析几个相对重要的扩展:
1)key_share
key_share 是椭圆曲线类型对应的公钥,如图所示
此处包含一个KeyShareEntry,是x25519曲线组,这是客户端生成的代表自己支持的DH组,具体数据在KeyExchange字段中;每个KeyShareEntry都代表一组密钥交换参数,对于有限域DH来说是g和p的值,对于椭圆域DH是椭圆曲线和基点的值,很明显这里是用了椭圆曲线的DH。同选定加密组件一样,TLS 1.3定义了几组gp值,双方只需要协商想要使用的gp对即可。具体实施过程,为每个组生成一个DH密钥交换的参数,将其组名和参数值封装在key_share扩展中,服务端选定DH组后,返回一个封装好的key_share,双方根据交换的公钥参数和自己持有的私钥参数计算出DH最终密钥。
理论上,客户端应该将所有与密钥协商有关的扩展(pre_shared_key、shared_key)都发送给服务端,服务端选定哪一种,再将对应选定的扩展返还给客户端,如果服务端同时使用两种密钥协商,则返还所有扩展,
然后我们来看下EXDH的密钥协商过程,首先EC的意思是椭圆曲线,这个EC提供了一个很厉害的性质,你在曲线上找一个点P,给定一个整数k,求解另外一个点Q=kP很容易,给定两个点P,Q,知道Q =kP,求k却是个难题。在这个背景下,给定一个大家都知道的大数G,client在每次需要和server协商秘钥时,生成一段随机数a,然后发送A=aG给server,server收到这段消息(aG)后,生成一段随机数b,然后发送B=bG给client,然后server端计算(aG)b作为对称秘钥,client端收到后bG后计算a(Gb),因为(aG)b = a(Gb),所以对称秘钥就是aGb啦,攻击者只能截获A=aG和B=bG,由于椭圆曲线难题,知道A和G是很难计算a和b的,也就无法计算aGb了(当然,实际上的计算过程和原理证明不是这么简单的,中间还有一个取模的过程,以及取模过程的交换律和结合律证明,但是本质思想和这个是差不多的)。留意下TLS1.3图中的key_share,这段的功能就是直接记录了aG,然后包含在client_hello中。然后server收到后在server_hello的key_share段中记录bG。所以TLS1.3一个RTT就搞定握手了。
参考:https://blog.csdn.net/zk3326312/article/details/80245756
2)signature_algorithms
Signature_algorithms扩展是,客户端提供签名算法,让服务器选择
以第一个签名算法为例,ecdsa_secp256r1_sha256,使用sha256作为签名中的哈希,签名算法为ecdsa。
3)psk_key_exchange_modes
TLS 1.3 与之前的协议有较大差异,相比过去的的版本,引入了新的密钥协商机制 — PSK。TLS 1.3支持DH、PSK两种密钥协商机制,也支持同时使用两者进行密钥协商。
psk_key_exchange_modes表示psk密钥交互模式选择
此处的PSK模式为(EC)DHE下的PSK(貌似就是使用上面的ECDHE进行密钥协商),客户端和服务器必须提供KeyShare。
如果是仅PSK模式,则服务器不需要提供KeyShare。
下面分析server hello:
可以看到Record layer下面有三个协议:
1.握手协议:确定了加密套件为TLS_AES_128_GCM_SHA256,确定了密钥协商的随机数bG.
2.密钥交换协议
3.应用数据协议-https
4).SNI Service name indication
由于服务器能力的增强,在一台物理服务器上部署多个虚拟主机已经成为十分流行的做法了。在过去的 HTTP 时代,解决基于名称的主机同一 ip 地址上托管多个网站的问题并不难。当一个客户端请求某特定网站时,把请求的域名作为主机头(host)放在 http header 中,从而服务器根据域名可以知道把该请求引向哪个域名服务,并把匹配的网站传送给客户端。但是此方式到 https 就失效了,因为 SSL 在握手的过程中,不会有 host 信息,所以服务端通常返回配置中的第一个可用证书,这就导致不同虚拟主机上的服务不能使用不同证书(但在实际中,证书通常是与服务对应。)
所以通过这个字段我们有可能能识别出APP对应的服务是什么。
参考:
https://blog.csdn.net/u010217394/article/details/121713758
当APP检测到请求成功,即网站还能被访问时,会调用getAppConfig()。
继续跟踪this.mHomeApi.getAppConfig,发现它向服务器请求了一个json文件,猜测会将服务器返回的配置信息用来配置APP。
抓包如下:
发现本地请求的数据是TLS传输,下面能看到数据是加密的数值。
请求完之后进入回调函数如下:
跟踪judgeSkin
跟踪goHomePage:
这里就会跳转到mainactivity,即APP的主页面MainActivity.class
并且结束当前页面。
以下即跳入主页面
以下我们看看MainActivity.class的oncreate函数
其gethost函数会执行以下函数:
继续跟踪getHost
跟踪getNextServerUrl
这里是请求服务器去获取/api/getAppConfig.do这个配置文件(上面是获取/static/data/config.json文件)
到这里好像也没配置什么信息
下面我又点击了导航栏,跳转到其他页面
通过抓包发现,它又请求了其他的域名,并且后面和该域名进行了TCP连接,并且传输了加密的数据。
此时server name与上面不同
后面又有client hello请求
又出现了新的server name.
总结:
1.同一个APP会请求多个host的资源,所以在识别APP的时候不能只通过单一的IP流去识别。
2.在TLS1.3下只有协议头和握手信息能被看到,其他都是加密状态。
3.SNI字段是一个比较有用的信息。
intent
=
Intet(this,另一个activity)
startactivity(intent)
intent
=
Intet(this,另一个activity)
startactivity(intent)
intent
=
Intet(intent.action_view)
intent.data
=
uri.parse(
'www.baidu.com'
)
startactivity(intent)
intent
=
Intet(intent.action_view)
intent.data
=
uri.parse(
'www.baidu.com'
)
startactivity(intent)
findstr.exe
/
s
/
i
"0x7f10002f"
*
.
*
outdir\res\values\public.xml: <public
type
=
"string"
name
=
"activity_alipay_real_name_hint"
id
=
"0x7f10002f"
/
>
outdir\smali\com\happy\roulette\R$string.smali:.field public static final activity_alipay_real_name_hint:I
=
0x7f10002f
findstr.exe
/
s
/
i
"0x7f10002f"
*
.
*
outdir\res\values\public.xml: <public
type
=
"string"
name
=
"activity_alipay_real_name_hint"
id
=
"0x7f10002f"
/
>
outdir\smali\com\happy\roulette\R$string.smali:.field public static final activity_alipay_real_name_hint:I
=
0x7f10002f
apktool d xxx.apk
-
o baz
-
<activity android:name
=
"com.happy.roulette.activity.SplashActivity"
android:theme
=
"@style/Theme.AppCompat.Light.NoActionBar.FullScreen"
android:screenOrientation
=
"portrait"
>
-
<intent
-
filter
>
<action android:name
=
"android.intent.action.MAIN"
/
>
<category android:name
=
"android.intent.category.LAUNCHER"
/
>
<
/
intent
-
filter
>
<
/
activity>
-
<activity android:name
=
"com.happy.roulette.activity.SplashActivity"
android:theme
=
"@style/Theme.AppCompat.Light.NoActionBar.FullScreen"
android:screenOrientation
=
"portrait"
>
-
<intent
-
filter
>
<action android:name
=
"android.intent.action.MAIN"
/
>
<category android:name
=
"android.intent.category.LAUNCHER"
/
>
<
/
intent
-
filter
>
<
/
activity>
protected void onCreate(@Nullable Bundle paramBundle) {
super
.onCreate(paramBundle);
setContentView(
2131492944
);
/
/
加载定义好的布局
TextView textView
=
(TextView)_$_findCachedViewById(R.
id
.tv_version_info);
/
/
设置文字
Intrinsics.checkExpressionValueIsNotNull(textView,
"tv_version_info"
);
textView.setText(
"37_2.2.40"
);
/
/
设置文字
checkLocalHost();
}
protected void onCreate(@Nullable Bundle paramBundle) {
super
.onCreate(paramBundle);
setContentView(
2131492944
);
/
/
加载定义好的布局
TextView textView
=
(TextView)_$_findCachedViewById(R.
id
.tv_version_info);
/
/
设置文字
Intrinsics.checkExpressionValueIsNotNull(textView,
"tv_version_info"
);
textView.setText(
"37_2.2.40"
);
/
/
设置文字
checkLocalHost();
}
private final void checkLocalHost() {
String
str
=
HostManager.INSTANCE.loadHostUrl();
/
/
先取出url
HostManager.INSTANCE.setNeedGetHost(true);
checkAppMaintain(
str
, true);
/
/
然后验证url是否能连接
}
private final void checkLocalHost() {
String
str
=
HostManager.INSTANCE.loadHostUrl();
/
/
先取出url
HostManager.INSTANCE.setNeedGetHost(true);
checkAppMaintain(
str
, true);
/
/
然后验证url是否能连接
}
public final
class
HostManager {
public final String loadHostUrl() {
mSharedPreferencesManager
=
new SharedPreferencesManager(MyApplication.getAppContext());
SharedPreferencesManager sharedPreferencesManager
=
mSharedPreferencesManager;
if
(sharedPreferencesManager
=
=
null)
Intrinsics.throwUninitializedPropertyAccessException(
"mSharedPreferencesManager"
);
return
sharedPreferencesManager.get(
"key-host-url"
, "");
}
public final
class
HostManager {
public final String loadHostUrl() {
mSharedPreferencesManager
=
new SharedPreferencesManager(MyApplication.getAppContext());
SharedPreferencesManager sharedPreferencesManager
=
mSharedPreferencesManager;
if
(sharedPreferencesManager
=
=
null)
Intrinsics.throwUninitializedPropertyAccessException(
"mSharedPreferencesManager"
);
return
sharedPreferencesManager.get(
"key-host-url"
, "");
}
C:\Users\Administrator>adb shell dumpsys window | findstr mCurrentFocus
mCurrentFocus
=
Window{b007190 u0 com.cxinc.app.n9h
/
com.happy.roulette.activity.MainActivity}
C:\Users\Administrator>adb shell dumpsys window | findstr mCurrentFocus
mCurrentFocus
=
Window{b007190 u0 com.cxinc.app.n9h
/
com.happy.roulette.activity.MainActivity}
C:\Users\Administrator>adb shell dumpsys window | findstr mCurrentFocus
mCurrentFocus
=
Window{ae65cbc u0 com.cxinc.app.n9h
/
com.happy.roulette.activity.login.LoginActivity}
C:\Users\Administrator>adb shell dumpsys window | findstr mCurrentFocus
mCurrentFocus
=
Window{ae65cbc u0 com.cxinc.app.n9h
/
com.happy.roulette.activity.login.LoginActivity}
C:\Users\Administrator>adb devices
List
of devices attached
emulator
-
5554
device
C:\Users\Administrator>adb
-
s emulator
-
5554
shell
emulator64_x86_64:
/
$ run
-
as com.cxinc.app.n9h
run
-
as: package
not
debuggable: com.cxinc.app.n9h
C:\Users\Administrator>adb devices
List
of devices attached
emulator
-
5554
device
C:\Users\Administrator>adb
-
s emulator
-
5554
shell
emulator64_x86_64:
/
$ run
-
as com.cxinc.app.n9h
run
-
as: package
not
debuggable: com.cxinc.app.n9h
private final void checkAppMaintain(String paramString, boolean paramBoolean) {
this.mHostApi.checkUrl(paramString, new SplashActivity$checkAppMaintain$
1
(paramString, paramBoolean));
}
private final void checkAppMaintain(String paramString, boolean paramBoolean) {
this.mHostApi.checkUrl(paramString, new SplashActivity$checkAppMaintain$
1
(paramString, paramBoolean));
}
public void checkUrl(String paramString, BaseWebApi.ResultListener paramResultListener) {
this.mAppUrl
=
paramString;
StringBuilder stringBuilder
=
new StringBuilder();
stringBuilder.append(this.mAppUrl);
stringBuilder.append(
"/api/checkAppWh.do"
);
StringRequest stringRequest
=
createStringRequest(
0
, stringBuilder.toString(), null, paramResultListener);
getRequestQueue().add((Request)stringRequest);
}
public void checkUrl(String paramString, BaseWebApi.ResultListener paramResultListener) {
this.mAppUrl
=
paramString;
StringBuilder stringBuilder
=
new StringBuilder();
stringBuilder.append(this.mAppUrl);
stringBuilder.append(
"/api/checkAppWh.do"
);
StringRequest stringRequest
=
createStringRequest(
0
, stringBuilder.toString(), null, paramResultListener);
getRequestQueue().add((Request)stringRequest);
}
C:\Users\Administrator>adb shell
C:\Users\Administrator>adb shell
emulator64_x86_64:
/
C:\Users\Administrator>adb pull
/
sdcard
/
capture.pcap d:
/
capture.pcap
/
sdcard
/
capture.pcap:
1
file
pulled,
0
skipped.
2.6
MB
/
s (
108623
bytes
in
0.040s
)
C:\Users\Administrator>adb pull
/
sdcard
/
capture.pcap d:
/
capture.pcap
/
sdcard
/
capture.pcap:
1
file
pulled,
0
skipped.
2.6
MB
/
s (
108623
bytes
in
0.040s
)
public static final
class
SplashActivity$checkAppMaintain$
1
implements BaseWebApi.ResultListener {
SplashActivity$checkAppMaintain$
1
(String param1String, boolean param1Boolean) {}
/
/
如果请求失败了调用OnError,说明当前请求的域名失效了
public void onError(@NotNull ErrorOutput param1ErrorOutput) {
Intrinsics.checkParameterIsNotNull(param1ErrorOutput,
"error"
);
Log.e(
"SplashActivity"
, "APP );
if
(this.$isFailToGetHost) {
SplashActivity.this.getHost();
/
/
调用这个获取新的url,然后发送请求:是https:
/
/
9h
.开头的
return
;
}
SplashActivity.this.showErrorRetryDialog(");
}
/
/
如果请求成功了:以下代码有两个case:case49,case48。
public void onResult(@NotNull String param1String) {
Context context;
Intrinsics.checkParameterIsNotNull(param1String,
"response"
);
Log.i(
"SplashActivity"
, "APP );
switch (param1String.hashCode()) {
case
49
:
if
(param1String.equals(
"1"
)) {
context
=
(Context)SplashActivity.this;
StringBuilder stringBuilder
=
new StringBuilder();
stringBuilder.append(WebServerUrl.getBaseUrl());
stringBuilder.append(
"/wh.html"
);
/
/
通过wh.html可以看出wh是维护的缩写,且下面也标识了“维护中”,
/
/
所以推测这是服务器维护时会返回一个code,此时会执行下面代码的跳转,
/
/
比如跳转到
9h
.app00app.com
/
wh.html
JumpUtil.ToWeb(context, stringBuilder.toString(), "维护中“);
SplashActivity.this.finish();
return
;
}
break
;
case
48
:
if
(context.equals(
"0"
)) {
StringBuilder stringBuilder
=
new StringBuilder();
stringBuilder.append(
"Selected host url: "
);
stringBuilder.append(this.$selectedHostUrl);
Log.i(
"SplashActivity"
, stringBuilder.toString());
WebServerUrl.setBaseUrl(this.$selectedHostUrl);
SplashActivity.this.getAppConfig();
/
/
将服务器返回的参数用来设置APP
return
;
}
break
;
}
onError(new ErrorOutput());
}
}
public static final
class
SplashActivity$checkAppMaintain$
1
implements BaseWebApi.ResultListener {
SplashActivity$checkAppMaintain$
1
(String param1String, boolean param1Boolean) {}
/
/
如果请求失败了调用OnError,说明当前请求的域名失效了
public void onError(@NotNull ErrorOutput param1ErrorOutput) {
Intrinsics.checkParameterIsNotNull(param1ErrorOutput,
"error"
);
Log.e(
"SplashActivity"
, "APP );
if
(this.$isFailToGetHost) {
SplashActivity.this.getHost();
/
/
调用这个获取新的url,然后发送请求:是https:
/
/
9h
.开头的
return
;
}
SplashActivity.this.showErrorRetryDialog(");
}
/
/
如果请求成功了:以下代码有两个case:case49,case48。
public void onResult(@NotNull String param1String) {
Context context;
Intrinsics.checkParameterIsNotNull(param1String,
"response"
);
Log.i(
"SplashActivity"
, "APP );
switch (param1String.hashCode()) {
case
49
:
if
(param1String.equals(
"1"
)) {
context
=
(Context)SplashActivity.this;
StringBuilder stringBuilder
=
new StringBuilder();
stringBuilder.append(WebServerUrl.getBaseUrl());
stringBuilder.append(
"/wh.html"
);
/
/
通过wh.html可以看出wh是维护的缩写,且下面也标识了“维护中”,
/
/
所以推测这是服务器维护时会返回一个code,此时会执行下面代码的跳转,
/
/
比如跳转到
9h
.app00app.com
/
wh.html
JumpUtil.ToWeb(context, stringBuilder.toString(), "维护中“);
SplashActivity.this.finish();
return
;
}
break
;
case
48
:
if
(context.equals(
"0"
)) {
StringBuilder stringBuilder
=
new StringBuilder();
stringBuilder.append(
"Selected host url: "
);
stringBuilder.append(this.$selectedHostUrl);
Log.i(
"SplashActivity"
, stringBuilder.toString());
WebServerUrl.setBaseUrl(this.$selectedHostUrl);
SplashActivity.this.getAppConfig();
/
/
将服务器返回的参数用来设置APP
return
;
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-12-31 15:55
被bigeast编辑
,原因: 更新了网盘链接