<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on Lotty&#39;s Blog</title>
    <link>https://lotty520.github.io/posts/</link>
    <description>Recent content in Posts on Lotty&#39;s Blog</description>
    <generator>Hugo -- 0.147.2</generator>
    <language>zn-ch</language>
    <lastBuildDate>Thu, 08 May 2025 15:18:26 +0800</lastBuildDate>
    <atom:link href="https://lotty520.github.io/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>欢迎</title>
      <link>https://lotty520.github.io/posts/hello-world/</link>
      <pubDate>Wed, 07 May 2025 19:15:57 +0800</pubDate>
      <guid>https://lotty520.github.io/posts/hello-world/</guid>
      <description>&lt;p&gt;这是一个专注于讨论风控的博客。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Random Access</title>
      <link>https://lotty520.github.io/posts/random-access/</link>
      <pubDate>Thu, 08 May 2025 15:18:26 +0800</pubDate>
      <guid>https://lotty520.github.io/posts/random-access/</guid>
      <description>&lt;h2 id=&#34;随机访问和内存地址连续性&#34;&gt;随机访问和内存地址连续性&lt;/h2&gt;
&lt;p&gt;在对集合的访问中，一般会有两种访问方式：随机访问和顺序访问，不同的底层数据结构，在使用这两种访问方式时，效率是有区别的。一般来讲，内存地址连续的数据结构，在随机访问时的效率比内存地址不连续的要高。因为内存地址连续，可以通过下标很快的检索出对应的元素，反之由于内存地址不连续，在随机访问时，需要从内存地址起始位置按照链接关系一个一个的扫描，效率自然就降低了。典型的例子就是数组和链表。数组是一种内存地址连续的结构，对它的随机访问，效率比较高；链表是一种内存地址不连续的结构，对它的随机访问效率就比较低。&lt;/p&gt;
&lt;h2 id=&#34;randomaccess接口&#34;&gt;RandomAccess接口&lt;/h2&gt;
&lt;p&gt;在java语言中，为了方便开发者选择高效的访问方式，系统提供了一个&lt;code&gt;RandomAccess&lt;/code&gt;接口，该接口用来标识对应的数据结构是否支持高效的随机访问。大多数以数组为底层结构的数据结构都实现了该接口。比如常见的&lt;code&gt;ArrayList&lt;/code&gt;。在官方声明中，也对该接口进行了很详细的描述，原文如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * Marker interface used by &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;tt&amp;gt;&lt;/span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/tt&amp;gt;&lt;/span&gt; implementations to indicate that
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * they support fast (generally constant time) random access.  The primary
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * purpose of this interface is to allow generic algorithms to alter their
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * behavior to provide good performance when applied to either random or
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * sequential access lists.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; *
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;The best algorithms for manipulating random access lists (such as
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;tt&amp;gt;&lt;/span&gt;ArrayList&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/tt&amp;gt;&lt;/span&gt;) can produce quadratic behavior when applied to
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * sequential access lists (such as &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;tt&amp;gt;&lt;/span&gt;LinkedList&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/tt&amp;gt;&lt;/span&gt;).  Generic list
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * algorithms are encouraged to check whether the given list is an
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;tt&amp;gt;&lt;/span&gt;instanceof&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/tt&amp;gt;&lt;/span&gt; this interface before applying an algorithm that would
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * provide poor performance if it were applied to a sequential access list,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * and to alter their behavior if necessary to guarantee acceptable
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * performance.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; *
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;It is recognized that the distinction between random and sequential
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * access is often fuzzy.  For example, some &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;tt&amp;gt;&lt;/span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/tt&amp;gt;&lt;/span&gt; implementations
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * provide asymptotically linear access times if they get huge, but constant
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * access times in practice.  Such a &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;tt&amp;gt;&lt;/span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/tt&amp;gt;&lt;/span&gt; implementation
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * should generally implement this interface.  As a rule of thumb, a
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;tt&amp;gt;&lt;/span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/tt&amp;gt;&lt;/span&gt; implementation should implement this interface if,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; * for typical instances of the class, this loop:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;该声明对于&lt;code&gt;RandomAccess&lt;/code&gt;接口进行很详细的描述，大致包括：&lt;/p&gt;</description>
    </item>
    <item>
      <title>关于设备指纹，你想了解的都在这里</title>
      <link>https://lotty520.github.io/posts/what-is-dfp/</link>
      <pubDate>Thu, 08 May 2025 10:15:57 +0800</pubDate>
      <guid>https://lotty520.github.io/posts/what-is-dfp/</guid>
      <description>&lt;h1 id=&#34;设备标识符&#34;&gt;设备标识符&lt;/h1&gt;
&lt;p&gt;在移动安全领域，设备唯一标识符策略，也就是设备指纹是一种比较常见的安全手段。设备指纹安全是一种实现简单、门槛较低、安全性较高的安全手段。在国内，有不少安全公司都推出了设备指纹产品，主要覆盖android、ios、js等平台。事实上，为了确保自身数据的安全性和保密性，很多大型企业都会有自身的设备指纹团队。作者在此仅讨论下移动端android平台的设备指纹特性。&lt;/p&gt;
&lt;h2 id=&#34;设备唯一标识符&#34;&gt;设备唯一标识符&lt;/h2&gt;
&lt;p&gt;在安卓端，原生系统其实已经提供了很多标识性比较强的设备ID字段，比如说：androidId、imei、imsi、serialNo等。在仅仅只需要对设备进行简单的分类的场景下，以上字段就足以满足业务需求了。不管是较低版本的android设备，还是最新版本的android设备，以上字段中都会有部分字段是可以采集得到的。在设备指纹领域，系统的目的是根据设备信息来生成低碰撞的设备ID。在初期的设备指纹系统中，基本都是采集系统的环境信息和设备的软硬件信息来构造设备ID，由于这些方案是在本地生成的设备ID，在设备进行一些刷机、恢复出厂设置后，设备ID就会发生改变，并且极容易被类似xposed等改机工具给绕过。因此、这种设备ID的生成方式很快就退出了舞台。&lt;/p&gt;
&lt;p&gt;目前市场上的设备指纹方案，一般都是通过前端采集、后端生成来实现的。这种方案的好处是设备ID存储在后端，前端只负责数据采集，后端来校验设备ID，达到了前后端分离的目的。与此同时，设备ID生成策略被隐藏，不易被攻击，后端还可以通过长期演进的方式来保证较强能力的设备ID恢复能力。&lt;/p&gt;
&lt;h2 id=&#34;关于数据采集&#34;&gt;关于数据采集&lt;/h2&gt;
&lt;p&gt;在安卓设备中，可以体现出一个设备的身份的特征还是很多的。比如：androidId、imei、medi等原生字段，还有类似brand、deviceName、host等设备构建信息，还有abi、cpu、camera等硬件信息，还有wifi、blue、ip等网络信息。从目前的形式来看，谷歌对原生系统的权限收紧会导致越来越多的标识无法采集，这也是对设备指纹系统的一个巨大挑战。也因此，国内几个大型的安卓设备厂商组织了移动联盟，该联盟旨在在设备中从rom的层面，提供设备的唯一标识符。这些标识符是可以作为较稳定的设备ID来使用的。&lt;/p&gt;
&lt;p&gt;但是，我们必须得清楚，依靠某个单独的字段或者几个字段来标识设备的唯一性，是存在很大风险的。某些改机工具通过修改这些敏感的字段，就可以轻而易举的绕过我们方案。这也是设备指纹系统中一个很难解决的问题。当然这里面同样包括刷机、重启、恢复出厂设置等修改措施。&lt;/p&gt;
&lt;p&gt;针对以上的异常情形(也可以说是正常情形)，各个设备厂商也都研究了相应的措施，比如检测设备异常状态，多字段联合恢复等，这些手段在一定程度上提高了设备指纹的安全性。&lt;/p&gt;
&lt;h2 id=&#34;关于对抗&#34;&gt;关于对抗&lt;/h2&gt;
&lt;p&gt;客户端作为一种运行在用户设备中的系统，其面临的入侵风险是非常高的。也因此，客户端的对抗要求也会相对高很多。这些对抗手段主要集中在加解密、重打包、混淆策略、字符加密等常规手段，还包括一些逻辑本地化(Native c/c++)、虚拟化等强化手段。&lt;/p&gt;
&lt;p&gt;在加解密方面主要是对秘钥的保护，常用的方案有秘钥交换策略、一次一密策略、白盒加密策略。这些技术手段的初衷都是为了隐藏对称加密的秘钥，从而对原始数据进行保护。&lt;/p&gt;
&lt;p&gt;在重打包方面主要是对证书校验、原始重点方法堆栈信息的判断来实现的。目的是即时检测出应用中被注入的新代码和被删除的原始代码。&lt;/p&gt;
&lt;p&gt;至于混淆和字符串加密，则是从逆向的角度来考虑的，目的是增加代码的阅读难度，其中字符加密也是为了保护系统的一些敏感的常量两字符信息。&lt;/p&gt;
&lt;p&gt;当然，像系统本地化、虚拟化这些手段是在系统发展到一定程度必然要经历的。本地化代码可以极大地提高代码的破解难度和动态调试难度，间接的提高了破解成本。至于虚拟化，他虽然提高了系统的安全级别，但是多少也会降低系统的运行效率，该领域也是可以持续挖掘的区域之一。&lt;/p&gt;
&lt;h2 id=&#34;关于风险识别&#34;&gt;关于风险识别&lt;/h2&gt;
&lt;p&gt;在很多场景，比如登录、注册、支付等敏感场景，设备ID不仅仅提供了设备验证和判定的功能，它还可以提供设备风险识别的能力。最简单的就是模拟器判定了。这在初期的一些设备指纹系统中就有体现。其他的还有类似改机、被hook、设备农场等等。设备指纹系统从设备数据的角度来对该设备所处网络环境、所属的软件运行环境进行多维度的解析，来输出对应的风险标签。这在一定程度上，扩展了设备指纹的业务能力，增强了业务方的产品安全。&lt;/p&gt;
&lt;h2 id=&#34;关于设备恢复&#34;&gt;关于设备恢复&lt;/h2&gt;
&lt;p&gt;在设备指纹系统中，设备恢复是极核心的能力，所谓的恢复其实就是针对同一个设备，在它的环境、状态等发生改变时，设备指纹系统仍然能够将之恢复成之前的设备。在上文提到过，设备本身就具有很多稳定的指标，比如：imei、wifimac等。通过这些指标的对比，我们就可以实现一套不错的设备恢复逻辑。但是，需要指出的是，这种方案需要对全版本的安卓设备进行适配，并且在不同的版本中，采用不同的组合，这样才能达到一个相对靠谱的恢复能力。相比这种方案，我们还可以通过一些被动式的方式，来获取其他应用的设备层面的指标，来间接的对设备进行恢复。不管哪种方式，都需要开发者及时的适配版本、从版本特性中找到可以使用的信息。&lt;/p&gt;
&lt;p&gt;在这里，我们提供一种可以长期演进的恢复方案。通过对设备的总体信息进行分析匹配，来进行设备ID的恢复。大致策略是对设备的一些重要字段进行对比，综合设备间的对比结果来判定是否可以获取到相似度极高的设备。通过这种方式来对设备进行一个概率性的恢复。&lt;/p&gt;
&lt;p&gt;这种方案的优点很明显，不是依赖一个或者一组数据来进行恢复，而是依靠一个设备的全量(或者大部分)指标来进行恢复的，并且这种相似性的计算规则可以进行迭代华的持续演进，及时适用新环境；它的缺点就是可能产生碰撞，而且无法对数据修改较多的设备进行恢复。&lt;/p&gt;
&lt;h2 id=&#34;完美方案&#34;&gt;完美方案&lt;/h2&gt;
&lt;p&gt;在笔者看来，设备指纹的完美解决方案是由系统提供者来处理的。底层系统提供者(谷歌、ROM厂商、硬件厂商等)可以在系统底层设定不可更改的标识信息，在硬件层烧制不可更改的标识符。在最底层，它们一方面拥有极高的系统权限，另一方面可以在系统层面上抵抗绝大部分侵入。由这些底层系统提供者提供的标识信息相对来说会靠谱得多。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
