查看原文
其他

从分析一个赌球APP中入门安卓逆向、开发、协议分析

bigeast 看雪学苑 2022-07-01


本文为看雪论坛优秀文章
看雪论坛作者ID:bigeast


分析要用到的工具:

1、Android studio(需要安装好模拟器)
2、WINRAR压缩包(提取apk文件中的.dex文件)
3、apktool(用WINRAR提取APK中的androidmanifest.xml文件时可能会导致乱码,所以要用它来提取)
4、d2j-dex2jar(将.dex文件转为.jar文件,后面会提到)
5、jd.gui(将.jar文件打开展示成java源代码,后面会提到)
6、Wireshark(用于分析流量)



1


APP开发的背景知识的介绍


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(),直接使用元素的名字就可以调用该元素了。

(1)逻辑间如何跳转---intent

在activity中按钮的listen函数中定义
intent = Intet(this,另一个activity)startactivity(intent)
即可实现跳转页面。

(2)隐式intent

不指定跳转到哪个activity,而是指定跳转动作和类型,让系统来选择合适的activity。我们可以在AndroidManifest.xml中设置activity的可以相应的动作和类型、相应的协议类型(scheme)。

intent不仅可以打开activity,也可以打开网页。
intent = Intet(intent.action_view)intent.data = uri.parse('www.baidu.com')startactivity(intent)
甚至可以通过intent向下一个或者上一个页面传递数据。

(3)常用UI控件---textview

在activity的xml中定义,显示文字。

width和height有三个可选值:
1、match_parent:和父布局大小一样(即和手机屏幕大小一样)
2、wrap_content:恰好包住里面内容。
3、固定值。

还有其他的属性可以选:是否居中,文字颜色,文字大小。



2


APP逆向过程


目标:给一个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文件中的代码。


查找某个字符串在哪个页面出现的小trick

res/values/string.xml存了APK的字符串。
同目录下的public.xml有其对应的id,查找当前目录下包含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


3


赌球APP分析实战


定位第一个APP界面

我们用apktool解析出apk的文件夹如下:(安装apktool前要先安装java1.8.关于apktool如何安装参考https://www.jianshu.com/p/b027856d55ac

安装完并配置好环境变量后,用以下命令反编译输出到baz目录。  
apktool d xxx.apk -o baz

从AndroidManifest.xml搜索android.intent.action.MAIN"定位到如下:

-<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>

所以第一个主页面是com.happy.roulette.activity.SplashActivity,以下便是第一个界面:


我们从com.happy.roulette.activity.SplashActivity.class的oncreate函数开始看(因为Oncreate函数是进入到一个新页面后要执行的第一个函数)。
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();}

我们继续看checkLocalHost()函数,每次启动第一个界面都会检查一下host。
private final void checkLocalHost() { String str = HostManager.INSTANCE.loadHostUrl();//先取出url HostManager.INSTANCE.setNeedGetHost(true); checkAppMaintain(str, true); //然后验证url是否能连接}
loadHostUrl函数会先取出url, 我们继续跟踪loadHostUrl。
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", ""); }

发现使用了sharepreferences存储这个url在本地,以下想在本地找到保存这个url的文件。

在模拟器上运行该APP,打印出APP的package和当前页面的acitivy,以下为APP在主页面时运行命令得到的结果。
C:\Users\Administrator>adb shell dumpsys window | findstr mCurrentFocus mCurrentFocus=Window{b007190 u0 com.cxinc.app.n9h/com.happy.roulette.activity.MainActivity}

以下为APP在登录页面时运行命令得到的结果:
C:\Users\Administrator>adb shell dumpsys window | findstr mCurrentFocus mCurrentFocus=Window{ae65cbc u0 com.cxinc.app.n9h/com.happy.roulette.activity.login.LoginActivity}

通过 run-as 命令进入APP的文件目录下:
C:\Users\Administrator>adb devicesList of devices attachedemulator-5554 deviceC:\Users\Administrator>adb -s emulator-5554 shellemulator64_x86_64:/ $ run-as com.cxinc.app.n9hrun-as: package not debuggable: com.cxinc.app.n9h

发现无法进入这个APP的目录,因为不是debug版本的APP。所以后面会通过抓包来获取这个url。
 
我们继续看checkLocalHost函数,上面我们无法分析出loadHostUrl在本地哪个文件获取url。

我们继续看后面的函数调用过程,通过loadHostUrl函数获取到url后,会调用checkAppMaintain来检查这个url。
private final void checkAppMaintain(String paramString, boolean paramBoolean) { this.mHostApi.checkUrl(paramString, new SplashActivity$checkAppMaintain$1(paramString, paramBoolean)); }

一直跟踪下去,发现这里会请求这个url。发现会在这个url后拼接/api/checkAppWh.do,然后发送请求。
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);}

tcpdump抓包

tcpdump是常用的一个抓包工具,linux或android环境下已经默认安装好。
当用android studio自带的模拟器启动APP后,在电脑终端输入adb shell进入模拟器终端:
C:\Users\Administrator>adb shell

进入模拟器终端后用tcpdump命令进行抓包,包保存在/sdcard/capture.pcap
emulator64_x86_64:/ # tcpdump -i any -p -n -s 0 -w /sdcard/capture.pcap


-i是指定网卡为any;

-w表示保存为pacp;

s 0 : tcpdump 默认只会截取前 96 字节的内容,要想截取所有的报文内容,可以使用 -s number, number 就是你要截取的报文字节数,如果是 0 的话,表示截取报文全部内容。

-p : 不让网络接口进入混杂模式。默认情况下使用 tcpdump 抓包时,会让网络接口进入混杂模式。一般计算机网卡都工作在非混杂模式下,此时网卡只接受来自网络端口的目的地址指向自己的数据。当网卡工作在混杂模式下时,网卡将来自接口的所有数据都捕获并交给相应的驱动程序。如果设备接入的交换机开启了混杂模式,使用 -p 选项可以有效地过滤噪声。
抓包结束后按Cltr+C中断后即可以保存文件。

我们在PC终端中把模拟器的抓取的包拿回来本地D盘,在本地终端执行。
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)

通过wireshark分析如下:

首先会进行DNS请求,获取这个域名对应的IP:发现请求9h.app00app.com这个域名,且IP为204.11.56.48。
checkmaintain执行完后会跳到下面这个回调函数里,即请求服务器后会回到以下函数。
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()); } }

发现请求这个IP,连接不上。所以会执行上面Onerror函数去获取另外一个域名。
Onerror函数会调用gethost()
private final void getHost() { this.mHostApi.getHost(new SplashActivity$getHost$1());}

继续跟踪:
public void getHost(BaseWebApi.ResultListener paramResultListener) { this.i = 0; this.mClientResultListener = paramResultListener; sendGetHostRequest(getNextServerUrl());}

看getNextServerUrl,它会将“https://9h.”拼接域名list中一个域名。
private String getNextServerUrl() { try { WebServerUrl.setCurrentServerUrl(WebServerUrl.SERVER_URL_LIST.get(this.i)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("https://9h."); stringBuilder.append(WebServerUrl.SERVER_URL_LIST.get(this.i)); stringBuilder.append("/api/getAppConfig.do"); return stringBuilder.toString(); } catch (Exception exception) { exception.printStackTrace(); return ""; }

跟踪SERVER_URL_LIST,发现这是一个域名的list,包含以下域名。猜测这种读博网站域名经常被封,所以要多准备几个域名。
public static final List<String> SERVER_URL_LIST = Arrays.asList(new String[] { "app00app.com", "app66app.com", "app99app.vip", "app66app.vip", "app88app.vip" });

获取到域名之后,又会继续去执行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()。
private final void getAppConfig() { TextView textView = (TextView)_$_findCachedViewById(R.id.tv_progress_msg); Intrinsics.checkExpressionValueIsNotNull(textView, "tv_progress_msg"); textView.setText("正在获取平台配置,请稍等“); this.mHomeApi.getAppConfig(new SplashActivity$getAppConfig$1()); }

继续跟踪this.mHomeApi.getAppConfig,发现它向服务器请求了一个json文件,猜测会将服务器返回的配置信息用来配置APP。
public void getAppConfig(BaseWebApi.ResultListener paramResultListener) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(WebServerUrl.getBaseUrl()); stringBuilder.append("/static/data/config.json"); StringRequest stringRequest = createStringRequest(0, stringBuilder.toString(), null, paramResultListener); getRequestQueue().add((Request)stringRequest);}

抓包如下:


发现本地请求的数据是TLS传输,下面能看到数据是加密的数值。
 
请求完之后进入回调函数如下:
public static final class SplashActivity$getAppConfig$1 implements BaseWebApi.ResultListener { //请求失败 public void onError(@NotNull ErrorOutput param1ErrorOutput) { Intrinsics.checkParameterIsNotNull(param1ErrorOutput, "error"); Log.e("SplashActivity", "Gat App Config ); SplashActivity.this.showErrorRetryDialog("); } //请求成功,则这里传进来的参数param1String即为服务器返回的响应。 public void onResult(@NotNull String param1String) { Intrinsics.checkParameterIsNotNull(param1String, "response"); Log.i("SplashActivity", "Gat App Config ); try { //把获取到json配置给APP AppConfigOutput appConfigOutput = (AppConfigOutput)(new Gson()).fromJson(param1String, AppConfigOutput.class); AppConfigManager.INSTANCE.setAppConfig(appConfigOutput); SplashActivity splashActivity = SplashActivity.this; String str = appConfigOutput.defaultSkin; Intrinsics.checkExpressionValueIsNotNull(str, "appConfigOutput.defaultSkin"); //这里就会跳转到mainactivity,即APP的主页面 splashActivity.judgeSkin(str); return; } catch (Exception exception) { exception.printStackTrace(); onError(new ErrorOutput()); return; } }}

跟踪judgeSkin:
private final void judgeSkin(String paramString) { if (AppConfigManager.INSTANCE.loadIsShowDefaultSkin()) { if (Intrinsics.areEqual(paramString, SystemSettingsManager.SkinStyle.BLUE.toString())) { SystemSettingsManager.INSTANCE.setColorSkin(SystemSettingsManager.SkinStyle.BLUE); } else if (Intrinsics.areEqual(paramString, SystemSettingsManager.SkinStyle.RED.toString())) { SystemSettingsManager.INSTANCE.setColorSkin(SystemSettingsManager.SkinStyle.RED); } else if (Intrinsics.areEqual(paramString, SystemSettingsManager.SkinStyle.DARK.toString())) { SystemSettingsManager.INSTANCE.setColorSkin(SystemSettingsManager.SkinStyle.DARK); } AppConfigManager.INSTANCE.saveIsShowDefaultSkin(false); } goHomePage(); }

跟踪goHomePage:这里就会跳转到mainactivity,即APP的主页面MainActivity.class,并且结束当前页面。
private final void goHomePage() { if (!isFinishing()) { startActivity(new Intent((Context)this, MainActivity.class)); finish(); }

以下即跳入主页面:


以下我们看看MainActivity.class的oncreate函数:
protected void onCreate(@Nullable Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2131492931); //设置布局 initFragment(); initNavigation(); //设置导航栏 setNavigationListener(); //设置导航栏的监听 if (HostManager.INSTANCE.isNeedGetHost()) getHost(); }

其gethost函数会执行以下函数:
private final void getHost() { (new HostApi()).getHost(new MainActivity$getHost$1());}

继续跟踪getHost:
public void getHost(BaseWebApi.ResultListener paramResultListener) { this.i = 0; this.mClientResultListener = paramResultListener; sendGetHostRequest(getNextServerUrl()); }}

跟踪getNextServerUrl,这里是请求服务器去获取/api/getAppConfig.do这个配置文件(上面是获取/static/data/config.json文件)。
private String getNextServerUrl() { try { WebServerUrl.setCurrentServerUrl(WebServerUrl.SERVER_URL_LIST.get(this.i)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("https://9h."); stringBuilder.append(WebServerUrl.SERVER_URL_LIST.get(this.i)); stringBuilder.append("/api/getAppConfig.do"); return stringBuilder.toString(); } catch (Exception exception) { exception.printStackTrace(); return ""; }}

到这里好像也没配置什么信息。
public static final class MainActivity$getHost$1 implements BaseWebApi.ResultListener { public void onError(@NotNull ErrorOutput param1ErrorOutput) { Intrinsics.checkParameterIsNotNull(param1ErrorOutput, "error"); Log.e("MainActivity", "Get Host ); MainActivity.this.showErrorCloseDialog("获取服务器失败,请先检查网络“); } public void onResult(@NotNull String param1String) { Intrinsics.checkParameterIsNotNull(param1String, "resultAppUrl"); Log.i("MainActivity", "Get Host ); if ((Intrinsics.areEqual(param1String, HostManager.INSTANCE.loadHostUrl()) ^ true) != 0) { HostManager.INSTANCE.saveHostUrl(param1String); MainActivity.this.showErrorCloseDialog("线路有更新,需要重启APP”); } }}

下面我又点击了导航栏,跳转到其他页面。


通过抓包发现,它又请求了其他的域名,并且后面和该域名进行了TCP连接,并且传输了加密的数据。



此时server name与上面不同,后面又有client hello请求。


又出现了新的server name。
 

4


总结


1、同一个APP会请求多个host的资源,所以在识别APP的时候不能只通过单一的IP流去识别。

2、在TLS1.3下只有协议头和握手信息能被看到,其他都是加密状态。

3、SNI字段是一个比较有用的信息。



E N D



 


看雪ID:bigeast

https://bbs.pediy.com/user-home-859945.htm

*本文由看雪论坛 bigeast 原创,转载请注明来自看雪社区





# 往期推荐

1.内核漏洞学习-HEVD-NullPointerDereference

2.一个数据加密恶意样本分析

3.windows64位分页机制分析-隐藏可执行内存方法

4.某系统漏洞挖掘之授权绕过到rce

5.office 分析笔记 —— rtf解析器(wwlib)的不完全解读

6.某系统漏洞挖掘之固件分析






点击“阅读原文”了解更多



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存