<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[Ljierui'Blog]]></title> 
<atom:link href="https://fuckdog.org/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[~安全小站~]]></description>
<link>https://fuckdog.org/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
    <title>常见的Android文件结构 - class.dex</title>
    <link>https://fuckdog.org/post-59.html</link>
    <description><![CDATA[<h1>class.dex文件</h1>
<p>class.dex 是 Android 应用的中间字节码文件，包含所有 Java/Kotlin 代码经编译后的指令集合，供 Android Runtime（ART/Dalvik）执行</p>
<h2>DEX的目录结构</h2>
<ul>
<li>主 DEX 文件：classes.dex（默认）。</li>
<li>MultiDEX 生成：classes2.dex，classes3.dex 等。</li>
<li>原生库和其他文件路径
<pre><code class="language-bash">├── classes.dex
├── classes2.dex
├── lib/        # 原生库（armeabi-v7a、arm64-v8a 等）
└── assets/     # 原始资源（未编译）</code></pre></li>
</ul>
<h2>DEX文件结构解析</h2>
<p>通过010editor编辑器打开class.dex文件，可以查看其结构<br />
DEX 文件由 多个结构化的 Section（区段） 组成，各 Section 通过头部（Header）定义的偏移量进行定位<br />
<a href="https://fuckdog.org/content/uploadfile/202502/10641740482580.png"><img src="https://fuckdog.org/content/uploadfile/202502/thum-10641740482580.png" alt="" /></a><br />
通过代码格式理解其文件格式</p>
<pre><code class="language-bach">struct DexFile {
    DexHeader          header;         // 文件头
    DexStringId        string_ids[];   // 字符串索引表
    DexTypeId          type_ids[];     // 类型索引表
    DexProtoId         proto_ids[];    // 方法原型索引表
    DexFieldId         field_ids[];    // 字段索引表
    DexMethodId        method_ids[];   // 方法索引表
    DexClassDef        class_defs[];   // 类定义表
    DexData            data;           // 数据区（字节码、注解等）
    DexLink            link_data;      // 静态链接数据（可选）
};
</code></pre>
<h2>各 Section 的详细结构</h2>
<h3>文件头（DexHeader）</h3>
<p>头部位于 DEX 文件起始位置，定义了所有 Section 的偏移量和数据规模，固定大小为 0x70 字节</p>
<ul>
<li>关键字段示例：
<ul>
<li>string_ids_size/string_ids_off：字符串表的数量和偏移。</li>
<li>class_defs_size/class_defs_off：类定义表的数量和偏移</li>
</ul></li>
</ul>
<pre><code class="language-c">struct DexHeader {
    u1  magic[8];           // Magic Number（"dex\n", 后跟版本标识，如 "035\0"）
    u4  checksum;           // 文件的 Adler-32 校验和（不含 magic 和 checksum 本身）
    u1  signature[20];      // SHA-1 哈希，用于防篡改
    u4  file_size;          // 整个文件的大小（字节）
    u4  header_size;        // 头部大小（固定 0x70）
    u4  endian_tag;         // 字节序标记（0x12345678 小端）
    u4  link_size;          // link_data 区段的大小（静态链接使用）
    u4  link_off;           // link_data 区段的偏移量
    u4  map_off;            // map_list 的偏移量（DexMapList 结构，描述各 Section 的位置）
    // ...其他字段定义各 Section 的偏移和数量
};</code></pre>
<h3>字符串索引表（DexStringId）</h3>
<ul>
<li>存储所有字符串在数据区的偏移量（去重处理）</li>
</ul>
<pre><code class="language-c">struct DexStringId {
    u4 string_data_off;    // 字符串数据的偏移量（指向 DexStringItem）
};</code></pre>
<ul>
<li>字符串数据格式（DexStringItem）:
<ul>
<li>字符串长度（LEB128 编码）+ UTF-8 数据（无终止符)</li>
</ul></li>
</ul>
<pre><code class="language-c">struct DexStringItem {
    uleb128  utf16_size;  // UTF-16 字符数（并非字节长度！）
    u1       data[];      // UTF-8 字节流
};</code></pre>
<h3>类型索引表（DexTypeId）</h3>
<ul>
<li>存储所有 类型描述符字符串的索引（指向字符串表）</li>
</ul>
<pre><code class="language-c">struct DexTypeId {
    u4 descriptor_idx;    // 对应字符串表中的索引（如 "Ljava/lang/String;"）
};</code></pre>
<h3>方法原型索引表（DexProtoId）</h3>
<ul>
<li>定义方法原型（包含返回类型和参数类型列表）</li>
</ul>
<pre><code class="language-c">struct DexProtoId {
    u4 shorty_idx;       // 短描述符索引（如 "VI" 表示 void func(int)）
    u4 return_type_idx;  // 返回值类型索引（对应 DexTypeId）
    u4 parameters_off;   // 参数类型列表偏移（指向 DexTypeList）
};</code></pre>
<ul>
<li>参数列表（DexTypeList）</li>
</ul>
<pre><code class="language-c">struct DexTypeList {
    u4          size;       // 参数数量
    DexTypeItem list[size]; // 参数类型数组
};
struct DexTypeItem {
    u2 type_idx;           // 类型索引（DexTypeId）
};</code></pre>
<h3>字段/方法索引表（DexFieldId / DexMethodId）</h3>
<ul>
<li>
<p>定义字段和方法的元信息</p>
<ul>
<li>
<p>DexFieldId 结构</p>
<pre><code class="language-c">struct DexFieldId {
u2 class_idx;        // 所属类索引（DexTypeId）
u2 type_idx;         // 字段类型索引（DexTypeId）
u4 name_idx;         // 字段名索引（DexStringId）
};</code></pre>
</li>
<li>
<p>DexMethodId 结构</p>
<pre><code class="language-c">struct DexMethodId {
u2 class_idx;        // 所属类索引（DexTypeId）
u2 proto_idx;        // 方法原型索引（DexProtoId）
u4 name_idx;         // 方法名索引（DexStringId）
};</code></pre>
</li>
</ul>
</li>
</ul>
<h3>类定义表（DexClassDef）</h3>
<ul>
<li>描述类的完整元数据
<pre><code class="language-c">struct DexClassDef {
u4 class_idx;         // 类类型索引（DexTypeId）
u4 access_flags;      // 访问标志（public, final, etc.）
u4 superclass_idx;    // 父类索引（DexTypeId）
u4 interfaces_off;    // 接口列表偏移（DexTypeList）
u4 source_file_idx;   // 源文件名索引（DexStringId，可选）
u4 annotations_off;   // 注解信息偏移（DexAnnotationsDirectoryItem，可选）
u4 class_data_off;    // 类数据偏移（DexClassData 结构）
u4 static_values_off; // 静态字段初始值偏移（DexEncodedArray）
};</code></pre></li>
</ul>
<h3>类数据（DexClassData）</h3>
<ul>
<li>存储类的实际字段和方法数据，包含可变长度结构（LEB128 编码压缩）</li>
</ul>
<pre><code class="language-c">struct DexClassData {
    uleb128 static_fields_size;   // 静态字段数量
    uleb128 instance_fields_size; // 实例字段数量
    uleb128 direct_methods_size;  // 直接方法数量（static + private）
    uleb128 virtual_methods_size; // 虚方法数量
    DexField[static_fields_size];     // 静态字段数组
    DexField[instance_fields_size];   // 实例字段数组
    DexMethod[direct_methods_size];   // 直接方法数组
    DexMethod[virtual_methods_size];  // 虚方法数组
};

// 字段定义（动态解析）
struct DexField {
    uleb128 field_idx;    // 字段索引（DexFieldId）
    uleb128 access_flags; // 访问标志
};

// 方法定义（动态解析）
struct DexMethod {
    uleb128 method_idx;   // 方法索引（DexMethodId）
    uleb128 access_flags; // 访问标志
    uleb128 code_off;     // 方法代码偏移（DexCode 结构）
};
</code></pre>
<h3>方法代码（DexCode）</h3>
<ul>
<li>存储方法的字节码、寄存器信息、异常表等
<pre><code class="language-c">struct DexCode {
u2  registers_size;     // 使用寄存器总数
u2  ins_size;           // 输入参数占用的寄存器数
u2  outs_size;          // 输出参数占用的寄存器数（用于调用其他方法）
u2  tries_size;         // try-catch 块数量
u4  debug_info_off;     // 调试信息偏移（DexDebugInfoItem）
u4  insns_size;         // 指令数组的大小（以 2 字节为单位）
u2  insns[insns_size];  // Dalvik 指令数组
// 可选的 try-catch 数据（DexTryItem）和 handlers 信息
};</code></pre></li>
</ul>
<h3>关键数据结构特性</h3>
<ul>
<li>(1) 索引化设计
<ul>
<li>所有字符串、类型、方法等均通过 索引引用（而非直接存储），极大减少冗余数据。</li>
<li>例如：方法名通过 name_idx 指向 DexStringId 列表中的一个条目。</li>
</ul></li>
<li>(2) LEB128 编码
<ul>
<li>在变长字段（如 uleb128 类型）中使用 LEB128 (Little-Endian Base 128) 压缩编码：</li>
<li>目的：优化空间效率，尤其是在类数据（DexClassData）中，字段/方法数量大但实际值较小。</li>
</ul></li>
<li>(3) 紧密排列
<ul>
<li>各 Section 的数据在文件内紧密排列，通过 偏移量快速跳转，避免了不必要的 IO 开销</li>
</ul></li>
</ul>
<h2>DEX文件的验证与优化</h2>
<ul>
<li>验证过程</li>
<li>安装时验证（由 installd 触发）：
<ul>
<li>Magic Number 检查：确认文件头是否为有效 DEX 签名（64 65 78 0A → &quot;dex\n&quot;）。</li>
<li>Checksum/签名验证：比对 APK 签名中的 DEX 完整性。</li>
<li>结构校验：检查 DEX 偏移量是否正确，避免恶意篡改后的越界访问。</li>
</ul></li>
<li>Dalvik 字节码验证：
<ul>
<li>格式验证：方法指令是否符合规范。</li>
<li>类型验证：寄存器使用和类型匹配。</li>
<li>控制流验证：跳转指令是否合法。</li>
</ul></li>
<li>优化过程
<ul>
<li>工具：dex2oat（取代旧版 dexopt）</li>
</ul></li>
<li>模式：
<ul>
<li>AOT（Ahead-of-Time）：安装时将 DEX 编译为本地机器码（OAT 格式）。</li>
<li>JIT（Just-in-Time）：运行时动态优化高频代码</li>
</ul></li>
</ul>
<h2>DEX文件的修改</h2>
<ul>
<li>
<p>反编译与修改工具</p>
<ul>
<li>Apktool：
<ul>
<li>解包 APK → 得到 classes.dex。</li>
<li>使用 d2j-dex2jar 转换为 JAR，或直接修改 smali 代码。</li>
</ul></li>
</ul>
</li>
<li>
<p>baksmali/smali（汇编与反汇编）：</p>
<pre><code class="language-bash">baksmali classes.dex -o smali/           # DEX → smali 文本
smali smali/ -o modified_classes.dex     # 重编译为 DEX</code></pre>
</li>
<li>
<p>动态加载技术</p>
<pre><code class="language-java">// 使用 DexClassLoader 加载插件化 DEX
DexClassLoader loader = new DexClassLoader(
dexPath, optimizedDir, null, getClassLoader()
);
Class&lt;?&gt; clazz = loader.loadClass("com.plugin.MyClass");
clazz.getMethod("invoke").invoke(null);</code></pre>
</li>
</ul>
<h2>MultiDEX</h2>
<ul>
<li>问题背景
<ul>
<li>方法数限制：单个 DEX 文件最多支持 65536 (2^16) 个方法引用（源于 DEX 格式设计）。</li>
<li>触发条件：大型应用依赖库过多（如 Google Play Services、Facebook SDK 等）→ 生成多个 DEX 文件。</li>
</ul></li>
<li>启用Multidex
<pre><code class="language-BASH">&lt;GRADLE&gt;
// build.gradle
android {
defaultConfig {
    multiDexEnabled true
}
}
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
}</code></pre></li>
</ul>]]></description>
    <pubDate>Mon, 24 Feb 2025 19:57:23 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-59.html</guid>
</item>
<item>
    <title>常见的Android文件格式 - APK</title>
    <link>https://fuckdog.org/post-58.html</link>
    <description><![CDATA[<h2>APK的生成流程</h2>
<p>APK 的生成是 代码编译、资源处理、打包签名 的流水线式过程</p>
<pre><code class="language-bash">Java/Kotlin 代码      资源文件
      ↓                 ↓
  编译为字节码      资源编译（AAPT2）
      ↓                 ↓
  .class → DEX      生成 resources.arsc
          ↘          ↓         ↙
            APK 打包（zip 格式）
                 ↓
             签名（Jarsigner/APK Signer）
                 ↓
             最终 APK/ZIP 对齐优化（zipalign）
</code></pre>
<h2>Java/Kotlin代码编译</h2>
<ul>
<li>输入：.java/.kt文件、依赖库(AAR/JAR)</li>
<li>工具：javac(Java)、kotlinc(Kotlin)</li>
<li>输出: .class字节码文件</li>
<li>关键过程: 
<ul>
<li>Java -&gt; .class(JVM字节码)</li>
<li>Kotlin -&gt; .class(通过Kotlin编译器)</li>
</ul></li>
</ul>
<h2>DEX转换</h2>
<ul>
<li>工具：D8或R8(取代旧版DX)</li>
<li>输入: 所有.class文件</li>
<li>输出: 单个/多个class.dex文件</li>
<li>作用: 将JVM字节码转为Android设备可执行的DEX格式
<pre><code class="language-bash">d8 --output ./out/ app.jar library.jar</code></pre></li>
</ul>
<h2>资源处理阶段</h2>
<p>资源编译与打包</p>
<ul>
<li>
<p>工具:AAPT2</p>
</li>
<li>
<p>输入: /res/*(图片、XML布局) , AndroidManifest.xml.</p>
</li>
<li>
<p>输出:</p>
<ul>
<li>resources.arsc(二进制资源索引表),res/转换为二进制格式（如 layout.xml -&gt; layout.bin）</li>
</ul>
</li>
<li>
<p>作用:</p>
<ul>
<li>资源ID分配</li>
<li>优化资源加载速度</li>
</ul>
</li>
</ul>
<pre><code class="language-bash"># 示例：AAPT2 编译资源
aapt2 compile -o out/ res/layout/activity_main.xml
aapt2 link -o base.apk --manifest AndroidManifest.xml -I android.jar</code></pre>
<h2>APK打包阶段</h2>
<ul>
<li>工具:zip格式打包(通过Gradle或apkbuilder)</li>
<li>输入: DEX、资源文件、原生库(.so)、AndroidManifest.xml</li>
<li>输出: 未签名的APK（包含所有组件）</li>
<li>文件结构
<pre><code class="language-bash">APK
├── AndroidManifest.xml  # 二进制格式
├── classes.dex          # 可执行代码
├── res/                 # 二进制资源
├── resources.arsc       # 资源索引
└── lib/                 # .so 原生库（可选）</code></pre></li>
</ul>
<h2>APK签名阶段</h2>
<ul>
<li>目的:确保APK的完整性和开发者身份认证</li>
<li>工具:
<ul>
<li>jarsigner(传统工具)</li>
<li>apksigner(官方推荐,支持v2/v3签名方案)</li>
</ul></li>
<li>流程:
<ol>
<li>生成Keystore(密钥库)
<pre><code class="language-bash">keytool -genkey -v -keystore my.keystore -alias mykey -keyalg RSA -validity 1000</code></pre></li>
<li>对APK签名
<pre><code class="language-bash">apksigner sign --ks my.keystore --ks-key-alias mykey app-unsigned.apk</code></pre></li>
</ol></li>
</ul>
<h2>ZIP对齐优化</h2>
<ul>
<li>工具:zipalign</li>
<li>作用:调整APK中文件的对齐方式，以优化内存访问效率</li>
<li>命令:
<pre><code class="language-bash">zipalign -v 4 input.apk output-aligned.apk</code></pre></li>
</ul>
<h2>APK安装流程</h2>
<ul>
<li>用户触发安装APK-&gt;系统启动PackageInstaller界面。</li>
<li>复制APK到临时目录(如/data/app/vmdlxxx.tmp)。</li>
<li>解析APK信息(包名、权限等)-&gt; 验证签名和兼容性。</li>
<li>分配应用专属目录(如/data/app/&lt;包名&gt;-XXXX/) -&gt; 复制APK到该目录。</li>
<li>DEX优化(由dexopt转换为OAT文件)。</li>
<li>更新系统数据库(packages.xml) -&gt; 注册应用信息。</li>
<li>广播通知安装完成-&gt;应用状态生效。</li>
</ul>
<h3>详解内部运作机制</h3>
<h4>触发安装:前端交互</h4>
<ul>
<li>
<p>用户场景:用户打开APK文件,系统调用PackageInstaller应用。</p>
<ul>
<li>PackageManagerService（PMS）
<ul>
<li>功能：管理应用安装、卸载、权限授予。</li>
<li>启动时机：系统启动时由 SystemServer 初始化。</li>
</ul></li>
<li>核心方法
<pre><code class="language-java">public void installPackageAsUser(String apkPath, int flags, int userId);</code></pre></li>
</ul>
</li>
<li>
<p>系统行文:</p>
<ul>
<li>Intent.ACTION_VIEW → 交由 PackageInstallerActivity 处理。</li>
<li>权限检查：校验安装来源（如 Settings.Secure.INSTALL_NON_MARKET_APPS）</li>
</ul>
</li>
</ul>
<h4>复制到临时目录</h4>
<ul>
<li>临时存储路径：/data/app/vmdlXXXXXX.tmp（由系统生成唯一文件名）</li>
<li>关键服务：PackageManagerService（PMS）的 installPackageAsUser 方法</li>
</ul>
<h4>APK解析与验证</h4>
<ul>
<li>解析内容：
<ul>
<li>提取 AndroidManifest.xml（转为二进制后解析）</li>
<li>读取包名、版本号、权限列表（uses-permission)、SDK 要求等</li>
</ul></li>
<li>签名验证：
<ul>
<li>检查签名证书的合法性（防止篡改）</li>
<li>对比现有签名（更新应用时需一致）</li>
</ul></li>
</ul>
<h4>应用目录分配</h4>
<ul>
<li>目标路径：/data/app/&lt;包名&gt;-XXXX/（存储 APK 和私有数据）。
<ul>
<li>示例：/data/app/com.example.app-1a2b3c/</li>
</ul></li>
<li>lib 目录：.so 文件解压到子目录 /lib/abi/（如 armeabi-v7a）</li>
<li>文件权限：设置 -rw-r--r--（用户私有，其他应用不可读）</li>
</ul>
<h4>DEX 优化（关键步骤）</h4>
<ul>
<li>流程：
<ul>
<li>原始 DEX 转换为 OAT 格式（ART 虚拟机优化）</li>
<li>输出至 /data/dalvik-cache/arm64/</li>
</ul></li>
<li>工具链：
<ul>
<li>dex2oat（编译器，将 DEX 转为机器码）</li>
<li>优化级别：speed（完全编译）或 quicken（快速优化）</li>
</ul></li>
</ul>
<h4>数据库更新</h4>
<ul>
<li>关键文件：
<ul>
<li>/data/system/packages.xml：存储包名、UID、权限、签名等信息。</li>
<li>/data/system/packages.list：记录包名与数据目录的映射。</li>
</ul></li>
<li>权限授权：将 uses-permission 声明的权限写入 PMS 内存映射。</li>
</ul>
<h4>广播通知安装结果</h4>
<ul>
<li>发送广播：Intent.ACTION_PACKAGE_ADDED → 通知系统和其他应用。</li>
<li>回调处理：系统服务更新状态（如 Launcher 刷新应用列表）</li>
</ul>]]></description>
    <pubDate>Tue, 11 Feb 2025 19:03:36 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-58.html</guid>
</item>
<item>
    <title>Android逆向 - Dalvik语言基础</title>
    <link>https://fuckdog.org/post-57.html</link>
    <description><![CDATA[<h1>Dalvik语言基础</h1>
<p>Dalvik虚拟机有专门的指令集及专门的指令格式和调用规范。由Dalvik指令集组成的代码称为Dalvik汇编代码，由这种代码组成的语言称为Dalvik汇编语言。</p>
<h1>Dalvik汇编的设计准则</h1>
<ol>
<li>采用基于寄存器的设计。方法在内存中创建后即拥有固定大小的栈帧，栈帧占用的空间取决于方法中指定的寄存器数目。运行时使用的数据和代码都存储在DEX文件中。</li>
<li>如果整数与浮点数按位表示，可以使用32位的寄存器来存放。相邻的两个寄存器表示64位，可用于存放大数据类型。寄存器不需要考虑对齐边界。</li>
<li>如果用来保存对象引用，寄存器必须能容纳引用类型。</li>
<li>不同的数据类型按位表示。</li>
<li>在调用约定上，使用N个寄存器来表示N个参数。对wide类型（32位）,使用相邻两个寄存器的组合来传递。对实例方法，第1个参数传递的是this指针。</li>
</ol>
<h1>Dalvik指令格式</h1>
<p>Dalvik指令组成，指令语法由指令的位描述和指令格式标识决定，约定如下:</p>
<ol>
<li>每16位的字用空格分开。</li>
<li>每个字母表示4位，每个字母按顺序从高字节到低字节排序，每4位之间可能使用竖线 “|” 将不同的内容分开。</li>
<li>顺序采用英文大写字母A~Z 表示4位的操作码。op表示8位的操作码。<br />
以指令格式&quot;A|G|op BBBB F|E|D|C&quot; 为例子,两个空格将指令分成了大小均为16位的三部分：</li>
<li>第1个16位部分是&quot;A|G|op&quot;,其高8位由&quot;A&quot;和&quot;G&quot;组成，低字节则由操作码&quot;op&quot;组成</li>
<li>第2个16位部分由&quot;BBBB&quot;组成，表示一个16位的偏移量</li>
<li>第3个16位部分由&quot;F&quot;E&quot;D&quot;C&quot; 四个4字节组成，它们分别表示寄存器的参数</li>
</ol>
<hr />
<p>单独使用位标识是无法确定一条指令的，必须通过指令格式标识来指定指令的格式编码，约定如下:</p>
<ol>
<li>指令格式标识大都由三个字符组成。其中，前两个是数字，最后一个是字母</li>
<li>第1个数字表示指令是由多少个16位的字组成</li>
<li>第2个数字表示指令最多使用的寄存器的个数。特殊标记r用于标识所使用的寄存器的范围</li>
<li>第3个字母为类型码，表示指令所使用的额外数据的类型|   |   |   |<br />
以指令格式标识&quot;22x&quot;为例，第1个数字2表示指令由两个16位字组成，第2个数字2表示指令使用两个寄存器，字母x表示没有使用额外的数据。</li>
</ol>
<hr />
<p>Dalvik指令对语法进行了一些说明，约定如下：</p>
<ol>
<li>每条指令都是从操作码开始的，后面紧跟参数。参数的个位不定，参数之间用逗号分隔。</li>
<li>每条指令的参数都是从指令的第一部分开始的。op位于低8位。高8位可以是一个8位的参数，也可以是两个4位的参数，还可以为空。如果指令超过16位，则将之后的部分依次作为参数。</li>
<li>如果参数采用&quot;vX&quot;的形式表示，说明它是一个寄存器，例如v0,v1等。这里用&quot;v&quot;而不是&quot;r&quot;的目的是避免与基于该虚拟机架构本身的寄存器产生命名冲突(例如,ARM架构的寄存器名称以&quot;r&quot;开头)</li>
<li>如果参数采用&quot;#+X&quot;的形式表示，说明它是一个常量数字</li>
<li>如果参数采用&quot;+X&quot;的形式表示，说明它是一个相对指令的地址偏移量</li>
<li>如果参数采用&quot;kind@X&quot;的形式表示，说明它是一个常量池索引值。其中&quot;kind&quot;表示常量池的类型，可以是string、type、field、meth<br />
以指令 &quot;op vAA, string@BBBB&quot; 为例，该指令使用了一个寄存器参数vAA,附加了一个字符串常量池索引值string@BBBB.</li>
</ol>
<hr />
<h1>Dex反编译工具</h1>
<p>dexdump : 命令 : dexdump -d Hello.dex<br />
baksmali ：命令 : baksmali -o baksmaliout Hello.dex<br />
区别:baksmali 使用的是p命名法; dexdump使用的是v命名法</p>
<h1>Dalvik寄存器</h1>
<p>Dalvik虚拟机是基于寄存器架构的，支持0-65535个寄存器，初始为v0</p>
<h1>寄存器的命名规则</h1>
<p>寄存器命名以&quot;v&quot;和&quot;p&quot;两种前缀来命名，两者的区别</p>
<h2>&quot;v&quot;命名法:</h2>
<ol>
<li>用于存储局部变量和方法内部的中间计算结果</li>
<li>这些寄存器是方法内部使用的，通常由 .locals 指令声明
<pre><code class="language-c">const v0, 0x1 // v0 和 v1 是局部变量寄存器，存储了常量和计算结果
add-int v1, v0, v0</code></pre></li>
</ol>
<h2>&quot;p&quot;命名法:</h2>
<ol>
<li>用于存储传递给方法的参数。这些寄存器也可以存储局部变量，但它们的初始作用是接收方法的输入参数</li>
<li>p 寄存器的数量和方法参数的数量有关，由 .param 指令或自动分配决定
<pre><code class="language-c">.param p1 // p1 是一个参数寄存器，存储了传递给方法的第一个参数
invoke-virtual {p1}, Ljava/lang/String;-&gt;length()I
move-result v0</code></pre></li>
</ol>
<h2>寄存器编号</h2>
<ol>
<li>v 寄存器的编号是从 0 开始的，表示局部变量。</li>
<li>p 寄存器的编号也从 0 开始，但它们是根据方法参数的顺序分配的。</li>
<li>静态方法：p0 是第一个参数。</li>
<li>实例方法：p0 通常表示 this 引用，而 p1 开始是实际参数</li>
</ol>
<h1>Dalvik字节码</h1>
<p>Dalvik字节码有自己的类型、方法及字段表示方法,这些内容与Dalvik虚拟机指令集一起组成了Dalvik汇编代码。<br />
Dalivk字节码只有两种类型基本类型和引用类型。用这两种类型来表示Java语言的全部类型,除了对象和数组属于引用类型，其他的Java类型都属于基本类型</p>
<ol>
<li>v:void(只用于返回值类型)</li>
<li>Z:boolean</li>
<li>B:byte</li>
<li>S:short</li>
<li>C:chat</li>
<li>I:int</li>
<li>J:Long</li>
<li>F:float</li>
<li>D:double</li>
<li>L:Java类类型</li>
<li>[:数组类型（多个[表示多维数组）</li>
</ol>
<h2>方法</h2>
<p>方法的表现比类型复杂一些。Dalvik使用方法名、类型参数与返回值来详细描述一个方法。格式如下：</p>
<pre><code class="language-c">    Lpackage/name/ObjectName;-&gt;MethodName(III)Z</code></pre>
<p>解释:&quot;Lpackage/name/ObjectName&quot;应该被理解为一个类型，MethodName为具体的方法名，(III)表示3个整型参数，Z表示boolean类型返回值<br />
baksmali生成的方法代码以.method指令开始,以.end method指令结果，根据方法类型的不同，在方法指令前可能会用&quot;#&quot;来添加注释。例如&quot;# virtual methods&quot;表示这是一个虚方法,&quot;# direct methods&quot;表示这是一个直接方法</p>
<h2>字段</h2>
<p>字段与方法相似，只是字段没有方法签名域中的参数和返回值，取代它们的是字段类型。格式如下</p>
<pre><code class="language-c">    Lpackage/name/ObjectName;-&gt;FieldName:Ljava/lang/String;</code></pre>
<p>解析:字段由类型(Lpackage/name/ObjectName)、字段名(FieldName)与字段类型(Ljava/lang/String)组成，字段名与字段类型用冒号分开。<br />
baksmail生成的字段代码以.field指令开头，根据字段类型的不同，在字段指令前可能会用&quot;#&quot;来添加注释。&quot;# instance fields&quot;这是实例字段，&quot;# static fields&quot;这是静态字段。</p>
<h1>Dalvik指令集</h1>
<h2>指令类型</h2>
<p>Dalvik指令模仿了C语言的调用约定。Dalvik指令的语法与助词符由如下特点。</p>
<ul>
<li>采用从目标到源的方式</li>
<li>根据字节码大小与类型的不同，为一些字节码添加了名称后缀以消除歧义。
<ul>
<li>32位常规类型的字节码未添加任何后缀</li>
<li>64位常规类型的字节码添加-wide后缀</li>
<li>对特殊类型的字节码，根据具体类型添加后缀，如-boolean,-byte,-char等。</li>
</ul></li>
<li>根据字节码布局与选项的不同，为一些字节码添加了字节码后缀以消除歧义，后缀通过在字节码主名称后添加斜杠分割。</li>
<li>在指令集的描述中，宽度值中的每个字母都表示4位的宽度。</li>
<li>以 &quot;move-wide/from16 vAA,vBBBB&quot; 为例子，move是基础字节码，表示一个基本操作；-wide为名称后缀，表示操作的数据宽度；from16为字节码后缀，表示源为一个16位寄存器引用变量;vAA为目的寄存器,它始终在源的前面,取值范围v0-v255;vBBBB为源寄存器，取值范围为v0-v65535</li>
<li>A~H 代表4位数值，表示0~15; AA~HH代表8位的数值,代表0~255；AAAA~HHHH代表16位数值，0~65535
<h2>空操作指令</h2>
<p>空操作指令的助记符为<code>nop</code>，它的值为00。nop指令通常用于代码对齐，不进行实际操作</p>
<h2>数据操作指令</h2>
<p>数据操作指令为<code>move</code>,根据字节码大小与类型不同有不同后缀：</p>
<ol>
<li>move vA,vB : 将vB寄存器的值赋予vA寄存器,源寄存器与目的寄存器都为4位。</li>
<li>move/from16 vAA,vBBBB : 将vBBBB寄存器的值赋予vAA寄存器,源寄存器为16位，目的寄存器为8位。</li>
<li>move/16 vAAAA,vBBBB : 将vBBBB寄存器的值赋予vAAAA寄存器，源寄存器与目的寄存器为16为。</li>
<li>move-wide vA,vB : 为4位的寄存器对赋值，源寄存器与目的寄存器都为4位。</li>
<li>move-wide/from16 vAA,vBBBB 与 move-wide/16 vAAAA,vBBBB 指令用于使move-wide相同。</li>
<li>move-object vA,vB : 为对象赋值，源寄存器与目的寄存器都为4位。</li>
<li>move-object/from16 vAA,vBBBB : 为对象赋值，源寄存器是16位，目的寄存器是8位。</li>
<li>move-object/16 vAAAA,vBBBB : 为对象赋值，源寄存器与目的寄存器都是16位的。</li>
<li>move-result vAA: 将上一个invo ke类型指令操作的单字非对象结果赋予vAA 寄存器。</li>
<li>move-result-wide vAA: 将上一个invo ke类型指令操作的双字非对象结果赋予vAA寄存器。</li>
<li>move-result-object vAA:将上一个invo ke类型指令操作的对象结果赋予vAA寄存器。</li>
<li>move-exception vAA : 将一个在运行时发生的异常保存到vAA寄存器中。这条指令必须在异常发生时由异常处理器使用，否则无效。</li>
</ol></li>
</ul>
<h2>返回指令</h2>
<p>返回指令是指函数结束时运行的最后一条指令，它的基础字节码为<code>return</code>,如下四条返回指令。</p>
<ol>
<li>return-void: 函数从1个void方法返回</li>
<li>return vAA : 函数返回一个32位非对象类型的值,返回值为8位寄存器vAA</li>
<li>return-wide vAA: 函数返回一个64位非对象类型的值，返回值为8位寄存器对vAA</li>
<li>return-object vAA : 函数返回一个对象类型的值，返回值为8位寄存器vAA</li>
</ol>
<h2>数据定义指令</h2>
<p>数据定义指令用于定义程序中常用到的常量、字符串、类等数据，它的基础字节码为<code>const</code>,例举如下:</p>
<ol>
<li>const/4 vA,#+B : 用于将数值符号扩展为32位后赋予寄存器vA</li>
<li>const/16 vAA, #+BBBB : 用于将数值符号扩展为32位后赋予寄存器vAA</li>
</ol>
<h2>锁指令</h2>
<p>锁指令多用在多线程程序对同一对象的操作中。Dalvik指令集中如下有两条锁指令</p>
<ol>
<li>monitor-enter vAA指令用于为指定对象获取锁。</li>
<li>monitor-exit vAA指令用于释放指定对象的锁。</li>
</ol>
<h2>实例操作指令</h2>
<p>与实例相关的操作包括实例的类型转换、检查及创建等</p>
<ol>
<li>check-cast vAA,type@BBBB ：用于将vAA寄存器中的对象引用转换成指定的类型，如果失败会抛出ClassCastException异常。</li>
<li>instance-of vA,vB,type@CCCC : 用于判断vB寄存器中的对象引用是否可以转换成指定的类型，如果可以就为vA寄存器赋值1，否则为0。</li>
<li>new-instance vAA,type@BBB ：构造一个指定类型对象的新实例，并将对象引用赋值给vAA寄存器。</li>
<li>check-cast/jumbo vAAAA,type@BBBBBB : 与check-cast vAA,type@BBB 指令相同，只是前者的寄存器与指令索引的取值范围更大。</li>
<li>instance-of/jumbo vAAAA,vBBBB,type@CCCCCCCC ：与instance-of vA,vB,type@CCCC 相同，前者的寄存器与索引的取值范围更大。</li>
<li>new-instance/jumbo vAAAA,type@BBBBBBBB : 与new-instance vAA,type@BBB相同，前者的寄存器与索引的取值范围更大。</li>
</ol>
<h2>数组操作指令</h2>
<p>数组操作包括获取数组长度、新建数组、数组赋值、数组元素取值与赋值等</p>
<ol>
<li>array-length vA,vB : 获取给定vB寄存器中数组的长度，并将值赋予vA寄存器。数组产犊值就是数组中条目的个数</li>
<li>new-array vA,vB type@CCCC ：构造指定类型(type@CCCC)和大小（vB）的数组，并将值赋予vA寄存器。</li>
<li>filled-new-array{vC,vD,vE,vF,vG},type@BBBB ：构造指定类型(type@BBBB) 和大小(vA)的数组并填充数组内容。vA寄存器时隐含使用的，除了指定数组的大小，还指定了参数的个数。</li>
<li>fill-array-data vAA,+BBBBBBBB : 用指定的数据来填充数组，vAA寄存器为数组引用，在指令后面会紧跟一个数据表。</li>
<li>new-array/jumbo vAAAA,vBBBB,type@CCCCCCCC : 与new-array vA,vB,type@CCCC指令相同，只是寄存器与指令索引的取值范围更大。</li>
<li>filled-new-aray/jumbo{vCCCC ... vNNNNN},type@BBBBBB : 与filled-new-array/range{} 相同，只是前者的指令索引的取值范围。<br />
7.arrayop vAA,vBB,vCC : 对vBB寄存器指定的数组元素进行取值与赋值。</li>
</ol>
<h2>异常指令</h2>
<p>Dalvik指令集中有一个用于抛出异常的指令, <code>throw vAA</code>:抛出vAA寄存器中指定类型的异常</p>
<h2>跳转指令</h2>
<p>跳转指令用于从当前地址跳转到指定的偏移处。Dalvik指令集中有三种跳转指令，分别是无条件跳转指令goto、分支跳转指令switch与条件跳转指令if。</p>
<ol>
<li>goto +AA : 用于无条件跳转到指定偏移处，偏移量AA不能为0.</li>
<li>goto/16 +AAAA : 用于无条件跳转到指定偏移处，偏移量AAAA不能为0.</li>
<li>goto/32 +AAAAAAAA : 用于无条件跳转到指定偏移处.</li>
<li>packed-switch vAA, +BBBBBBBB : 分支跳转指令，vAA寄存器位switch分支中需要判断的值,BBBBBBBB指向一个packed -switchpayload格式的偏移表</li>
<li>if-test vA,vB,+CCCC : 用于比较vA寄存器与vB寄存器的值</li>
<li>if-eq : 如果vA等于vB则跳转。</li>
<li>if-ne : 如果vA不等于vB则跳转。</li>
<li>if-lt : 如果vA小于vB则跳转</li>
<li>if-ge ：如果vA大于等于vB则跳转</li>
<li>if-gt : 如果vA大于vB则跳转</li>
<li>if-le ：如果vA小于等于vB则跳转</li>
</ol>
<h2>比较指令</h2>
<p>比较指令用于对两个寄存器的值(浮点型或长整型)进行比较，格式为<code>cmpkind vAA,vBB,vCC</code>,其中vBB与vCC是需要比较的两个寄存器或两个寄存器对，比较的结果将被放到vAA寄存器。</p>
<ol>
<li>cmpl-float : 用于比较两个单精度浮点数</li>
<li>cmpg-float : 用于比较两个单精度浮点数</li>
<li>cmpl-double : 用于比较两个双精度浮点数</li>
<li>cmpg-double ：用于比较两个双精度浮点数</li>
<li>cmp-long : 比较两个长整型数</li>
</ol>
<h2>字段操作指令</h2>
<p>字段操作指令用于对对象实例的字段进行读写操作，字段的类型可以是Java中有效的数据类型。<br />
对普通字段操作与静态字段操作，有两种指令集，分别是<code>iinstanceop vA,vB,field@CCCC</code>与<code>sstaticop vAA,field@BBBB</code></p>
<h2>方法调用指令</h2>
<p>方法调用指令负责调用类实例的方法，基础指令为<code>invoke</code>。方法调用指令有<code>invoke-kind {vC,vD,vE,vF,vG},meth@BBBB</code>与<code>invoke-kind/range{vCCCC..vNNNN},meth@BBBB</code>两类。这两类指令在作用上并无不同，只是后者在设置参数寄存器时使用range来指定寄存器的范围。根据方法类型不同，如下五条方法调用指令:</p>
<ol>
<li>invoke-virtual 或 invoke-virtual/range : 调用实例的虚方法</li>
<li>invoke-super 或 invoke-super/range : 调用实例的父类方法</li>
<li>invoke-direct 或 invoke-direct/range : 调用实例的直接方法</li>
<li>invoke-static 或 invoke-static/range : 调用实例的静态方法</li>
<li>invoke-interface 或 invoke-interface/range ：调用实例的接口方法</li>
</ol>
<h2>数据转换指令</h2>
<p>数据转换指令用于将一种类型的数值转换成另一个类型的数值，格式为unop vA,vB。在vB寄存器或vB寄存器对中存放了需要转换的数据。转换结果保存在vA寄存器或vA寄存器对中。数据转换指令列举如下</p>
<ol>
<li>neg-int : 用于对整型数求补</li>
<li>not-int ：用于对整数型求反</li>
<li>neg-long : 用于对长整型数求补</li>
<li>not-long : 用于对长整型数求反</li>
<li>neg-float : 用于对单精度浮点型数求补</li>
<li>neg-double : 用于对双精度浮点型数求补</li>
<li>int-to-long ： 将整型数转换为长整型数</li>
<li>int-to-float：将整型数转换为单精度浮点型数</li>
<li>int-to-double：将整型数转换为双精度浮点型数</li>
<li>long-to-int：将长整型数转换为整型数</li>
</ol>
<h2>数据运算指令</h2>
<p>数据运算指令包括算术运算指令与逻辑运算指令。</p>
<ol>
<li>add-int v0, v1, v2：v0 = v1 + v2（整数加法）。</li>
<li>add-int/2addr v0, v1：v0 += v1（将结果存储在同一寄存器中）。</li>
<li>sub-int v0, v1, v2：v0 = v1 - v2（整数减法）。</li>
<li>sub-int/2addr v0, v1：v0 -= v1。</li>
<li>mul-int v0, v1, v2：v0 = v1 * v2（整数乘法）。</li>
<li>mul-int/2addr v0, v1：v0 *= v1。</li>
<li>div-int v0, v1, v2：v0 = v1 / v2（整数除法）。</li>
<li>div-int/2addr v0, v1：v0 /= v1。</li>
<li>rem-int v0, v1, v2：v0 = v1 % v2（整数求余）。</li>
<li>rem-int/2addr v0, v1：v0 %= v1。</li>
<li>add-long v0, v1, v2：v0 = v1 + v2（长整型加法）。</li>
<li>add-long/2addr v0, v1：v0 += v1。</li>
<li>sub-long v0, v1, v2：v0 = v1 - v2（长整型减法）。</li>
<li>sub-long/2addr v0, v1：v0 -= v1。</li>
<li>mul-long v0, v1, v2：v0 = v1 * v2（长整型乘法）。</li>
<li>mul-long/2addr v0, v1：v0 *= v1。</li>
<li>div-long v0, v1, v2：v0 = v1 / v2（长整型除法）。</li>
<li>div-long/2addr v0, v1：v0 /= v1。</li>
<li>rem-long v0, v1, v2：v0 = v1 % v2（长整型求余）。</li>
<li>rem-long/2addr v0, v1：v0 %= v1。</li>
<li>add-float v0, v1, v2：v0 = v1 + v2（浮点数加法）。</li>
<li>add-float/2addr v0, v1：v0 += v1。</li>
<li>sub-float v0, v1, v2：v0 = v1 - v2（浮点数减法）。</li>
<li>sub-float/2addr v0, v1：v0 -= v1。</li>
<li>mul-float v0, v1, v2：v0 = v1 * v2（浮点数乘法）。</li>
<li>mul-float/2addr v0, v1：v0 *= v1。</li>
<li>div-float v0, v1, v2：v0 = v1 / v2（浮点数除法）。</li>
<li>div-float/2addr v0, v1：v0 /= v1。</li>
<li>rem-float v0, v1, v2：v0 = v1 % v2（浮点数求余）。</li>
<li>rem-float/2addr v0, v1：v0 %= v1。</li>
<li>add-double v0, v1, v2：v0 = v1 + v2（双精度加法）。</li>
<li>add-double/2addr v0, v1：v0 += v1。</li>
<li>sub-double v0, v1, v2：v0 = v1 - v2（双精度减法）。</li>
<li>sub-double/2addr v0, v1：v0 -= v1。</li>
<li>mul-double v0, v1, v2：v0 = v1 * v2（双精度乘法）。</li>
<li>mul-double/2addr v0, v1：v0 *= v1。</li>
<li>div-double v0, v1, v2：v0 = v1 / v2（双精度除法）。</li>
<li>div-double/2addr v0, v1：v0 /= v1。</li>
<li>rem-double v0, v1, v2：v0 = v1 % v2（双精度求余）。</li>
<li>rem-double/2addr v0, v1：v0 %= v1。</li>
<li>and-int v0, v1, v2：v0 = v1 &amp; v2。</li>
<li>and-int/2addr v0, v1：v0 &amp;= v1。</li>
<li>and-long v0, v1, v2：v0 = v1 &amp; v2（长整型）。</li>
<li>or-int v0, v1, v2：v0 = v1 | v2。</li>
<li>or-int/2addr v0, v1：v0 |= v1。</li>
<li>or-long v0, v1, v2：v0 = v1 | v2（长整型）。</li>
<li>xor-int v0, v1, v2：v0 = v1 ^ v2。</li>
<li>xor-int/2addr v0, v1：v0 ^= v1。</li>
<li>xor-long v0, v1, v2：v0 = v1 ^ v2（长整型）。</li>
<li>shl-int v0, v1, v2：v0 = v1 &lt;&lt; v2（整数左移）。</li>
<li>shl-int/2addr v0, v1：v0 &lt;&lt;= v1。</li>
<li>shr-int v0, v1, v2：v0 = v1 &gt;&gt; v2（整数算术右移）。</li>
<li>shr-int/2addr v0, v1：v0 &gt;&gt;= v1。</li>
<li>ushr-int v0, v1, v2：v0 = v1 &gt;&gt;&gt; v2（整数逻辑右移）。</li>
<li>ushr-int/2addr v0, v1：v0 &gt;&gt;&gt;= v1。</li>
</ol>
<h1>Dalvik指令练习</h1>
<p>实现输出HelloWorld</p>
<pre><code class="language-c">.class public LHelloWorld; #定义类名
.super Ljava/lang/Object; #定义父类
.method public static main([Ljava/lang/String;)V #声明静态main()方法
    .registers 4 #程序使用v0,v1,v2寄存器和一个参数寄存器
    .prologue   #代码起始指令
    # 空指令
    nop
    nop
    nop
    nop
    # 数据定义指令
    const/16 v0,0x8
    const/4 v1,0x5
    const/4 v2,0x3
    # 数据操作指令
    move v1,v2
    # 数组操作指令
    new-array v0,v0,[I
    array-length v1,v0
    # 实例操作指令
    new-instance v1,Ljava/lang/StringBuilder;
    # 方法调用指令
    invoke-direct{v1},Ljava/lang/StringBuilder;-&gt;&lt;init&gt;()V
    # 跳转指令
    if-nez v0,:cond_0
    goto: goto_0
    :cond_0
    # 数据转换指令
    int-to-float v2,v2
    # 数据运算指令
    add-float v2,v2,v2
    # 比较指令
    cmpl-float v0,v2,v2
    # 字段操作指令
    sget-object v0,Ljava/lang/System;-&gt;out:Ljava/io/PrintStream;
    const-string v1,"Hello World" # 构造字符串
    # 方法调用指令
    invoke-virtual{v0,v1},Ljava/io/PrintStream;-&gt;println(Ljava/lang/String;)V
    # 返回指令
    :goto_0
    return-void #返回空值
.end method
</code></pre>]]></description>
    <pubDate>Fri, 10 Jan 2025 10:17:29 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-57.html</guid>
</item>
<item>
    <title>Android逆向 - Dalvik可执行格式与字节码规范</title>
    <link>https://fuckdog.org/post-56.html</link>
    <description><![CDATA[<h1>Dalvik虚拟机</h1>
<h2>Dalvik虚拟机的特点</h2>
<ul>
<li>体积小，占用内存空间少。</li>
<li>专有的dex可执行文件格式，体积小，执行速度快。</li>
<li>常量池采用32位索引值，对类方法名、字段名、常量的寻址速度快。</li>
<li>基于寄存器架构，同时拥有一套完整的指令系统</li>
<li>提供了对象生命周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等机制。</li>
<li>Android程序都运行在Android系统中，每个进程都与一个Dalvik虚拟机实例对应。</li>
</ul>
<h2>Dalivk虚拟机与Java虚拟机的区别</h2>
<ul>
<li>运行的字节码不同
<ol>
<li>Java虚拟机运行的是Java字节码，Dalivk虚拟机运行的是Dalvik字节码。</li>
<li>Java程序编译后生成Java字节码保存在class文件中，Java虚拟机通过解码class文件运行程序。</li>
<li>Dalivk运行的是Dalvik字节码,所有的Dalvik字节码都是又Java字节码转换的，并打包到DEX文件中，Dalvik虚拟机解析DEX文件来执行这些字节码</li>
</ol></li>
<li>Dalivk 可执行文件的体积更小
<ol>
<li>在Android SDK中，<code>dx</code>工具会负责将Java字节码转换成Dalvik字节码。</li>
<li><code>dx</code>工具会对所有Java类文件中的常量池进行分解，消除冗余的的信息，重新组合成一个新的常量池，并让所有类文件共享这个常量池。</li>
</ol></li>
<li>虚拟机架构不同
<ol>
<li>Java虚拟机是基于栈架构，在程序运行时会频繁地对栈进行数据读写操作。</li>
<li>Dalvik虚拟机是基于寄存器结构的，数据的访问是直接在寄存器之间传递。</li>
</ol></li>
</ul>
<h2>Dalivk字节码与Java字节码对比</h2>
<p>1、编写Java代码</p>
<pre><code class="language-java">public class Hello {
    public int foo(int a, int b) {
        return (a + b) * (a - b);
    }

    public static void main(String[] args) {
        Hello hello = new Hello();
        System.out.println(hello.foo(5, 3));
    }
}</code></pre>
<p>2、编译成class ： javac hello.java<br />
3、把class文件通过Android SDK工具转码成Dex文件格式 ： d8  --output=d8out .\Hello.class<br />
4、查看Java字节码 ： javap -c -classpath . Hello<br />
5、查看Dalvik字节码： dexdump.exe -d .\classes.dex</p>
<p>Java字节码<br />
解析：</p>
<ol>
<li>下面的foo()函数 占8个字节,代码中每条指令都占一个字节，并且没有参数。</li>
<li>Java虚拟机的指令集也被称为零地址形式的指令集,零地址形式指令的源参数和目标参数都是隐含的，通过Java虚拟机提供的数据结构<code>求值栈</code>来传递。</li>
<li>对Java程序来说，每个线程在执行时都有一个PC计数器和一个Java栈。</li>
<li>看foo()函数, 左边的偏移量就是程序执行每行代码时PC寄存器的值</li>
<li><code>iload</code>是load指令中的一条，i是指令前缀，表示操作类型是int; load表示将局部变量存入Java栈。(类似指令：lload(long类型),fload(float类型),dload(double类型)入栈)</li>
<li>下划线右边的数字表示,要操作的是那个局部变量，索引值从0开始计数，例如<code>iload_1</code>表示使第2个int类型的局部变量入栈，而这个局部变量都是存放在局部变量区foo()函数中的第2个参数</li>
<li>
<p>下面的指令：<code>iload_2</code> 取第3个参数；<code>iadd</code>从栈顶弹出两个int类型的值并求它们的和；<code>iload_1</code>和<code>iload_2</code>分别再次把第2个参数和第3个参数压入栈;<code>isub</code>从栈顶弹出两个int类型的值并求它们的差；<code>imul</code>从栈顶弹出两个int类型的值并求它们的积；<code>ireturn</code>返回一个int类型的值。</p>
<pre><code class="language-c">Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
   0: aload_0
   1: invokespecial #1                  // Method java/lang/Object."&lt;init&gt;":()V
   4: return

public int foo(int, int);
Code:
   0: iload_1
   1: iload_2
   2: iadd
   3: iload_1
   4: iload_2
   5: isub
   6: imul
   7: ireturn

public static void main(java.lang.String[]);
Code:
   0: new           #2                  // class Hello
   3: dup
   4: invokespecial #3                  // Method "&lt;init&gt;":()V
   7: astore_1
   8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
  11: aload_1
  12: iconst_5
  13: iconst_3
  14: invokevirtual #5                  // Method foo:(II)I
  17: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
  20: return
}</code></pre>
</li>
</ol>
<p>Dalvik字节码<br />
解析:</p>
<ol>
<li>相比Java字节码, Dalvik字节码只用了4个步骤就完成了这个操作。</li>
<li><code>add-int</code> 将v2,v3 寄存器中的值相加，将结果保存到v0寄存器中;v2,v3表示foo()函数的第1个参数和第2个参数</li>
<li><code>sub-int</code> 将v2,v3 寄存器中的值相减, 将结果保存到第一个寄存器中，就是v2中（2addr 和sub-int v1 的区别，后者要多一个寄存器）</li>
<li><code>mul-int</code> 将v0，v2寄存器中的值相乘，结果保存在v0寄存器中</li>
<li>return 返回v0 寄存器的值</li>
<li>
<p>Dalvik 虚拟机在运行时也会为每个线程维护一个PC计数器和一个调用栈，与Java不同，这个调用栈维护了一个寄存器列表,寄存器数量会在方法结构体registers中给出。</p>
<pre><code class="language-c">Opened '.\classes.dex', DEX version '035'
Class #0            -
Class descriptor  : 'LHello;'
Access flags      : 0x0001 (PUBLIC)
Superclass        : 'Ljava/lang/Object;'
Interfaces        -
Static fields     -
Instance fields   -
Direct methods    -
#0              : (in LHello;)
  name          : '&lt;init&gt;'
  type          : '()V'
  access        : 0x10001 (PUBLIC CONSTRUCTOR)
  code          -
  registers     : 1
  ins           : 1
  outs          : 1
  insns size    : 4 16-bit code units
00016c:                                        |[00016c] Hello.&lt;init&gt;:()V
00017c: 7010 0400 0000                         |0000: invoke-direct {v0}, Ljava/lang/Object;.&lt;init&gt;:()V // method@0004
000182: 0e00                                   |0003: return-void
  catches       : (none)
  positions     :
    0x0000 line=1
  locals        :
    0x0000 - 0x0004 reg=0 this LHello;

#1              : (in LHello;)
  name          : 'main'
  type          : '([Ljava/lang/String;)V'
  access        : 0x0009 (PUBLIC STATIC)
  code          -
  registers     : 4
  ins           : 1
  outs          : 3
  insns size    : 17 16-bit code units
000184:                                        |[000184] Hello.main:([Ljava/lang/String;)V
000194: 2203 0100                              |0000: new-instance v3, LHello; // type@0001
000198: 7010 0000 0300                         |0002: invoke-direct {v3}, LHello;.&lt;init&gt;:()V // method@0000
00019e: 6200 0000                              |0005: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000
0001a2: 1251                                   |0007: const/4 v1, #int 5 // #5
0001a4: 1232                                   |0008: const/4 v2, #int 3 // #3
0001a6: 6e30 0100 1302                         |0009: invoke-virtual {v3, v1, v2}, LHello;.foo:(II)I // method@0001
0001ac: 0a03                                   |000c: move-result v3
0001ae: 6e20 0300 3000                         |000d: invoke-virtual {v0, v3}, Ljava/io/PrintStream;.println:(I)V // method@0003
0001b4: 0e00                                   |0010: return-void
  catches       : (none)
  positions     :
    0x0000 line=7
    0x0005 line=8
    0x0010 line=9
  locals        :
    0x0000 - 0x0011 reg=3 (null) [Ljava/lang/String;

Virtual methods   -
#0              : (in LHello;)
  name          : 'foo'
  type          : '(II)I'
  access        : 0x0001 (PUBLIC)
  code          -
  registers     : 4
  ins           : 3
  outs          : 0
  insns size    : 6 16-bit code units
000150:                                        |[000150] Hello.foo:(II)I
000160: 9000 0203                              |0000: add-int v0, v2, v3
000164: b132                                   |0002: sub-int/2addr v2, v3
000166: 9200 0002                              |0003: mul-int v0, v0, v2
00016a: 0f00                                   |0005: return v0
  catches       : (none)
  positions     :
    0x0000 line=3
  locals        :
    0x0000 - 0x0006 reg=1 this LHello;
    0x0000 - 0x0006 reg=2 (null) I
    0x0000 - 0x0006 reg=3 (null) I

source_file_idx   : 1 (Hello.java)</code></pre>
<h2>虚拟机的执行流程</h2>
<ol>
<li>Android 系统由Linux内核、函数库、Android运行时、应用程序框架及应用程序组成，Android系统结构采用了分层的思想，Dalvik虚拟机属于Android运行时环境，它与一些核心库一起承担了Android应用程序的运行工作</li>
<li>Android 系统启动并加载内核后，会立即执行init进程,在读取init.rc文件并启动系统中重要外部程序zygote.</li>
<li>Zygote是Android系统中所有进程的孵化器进程，它启动后，会初始化Dalvik虚拟机,再启动system_server进程并进入Zygote模式，通过socket等候命令的下达。</li>
<li>执行程序时，system_server进程通过Binder IPC方式将命令放送给Zygote,它收到命令后通过fork其自身创建一个Dalvik虚拟机的实例来执行应用程序的入口函数，从而启动程序。</li>
</ol>
</li>
</ol>
<h2>虚拟机的执行方式</h2>
<ol>
<li>即使编译又称为动态编译， 是通过在运行时将字节码翻译为机器码使程序的执行速度加快。</li>
<li>主流的JIT包括两种字节码编译方式：<br />
2.1、method方式: 以函数或方法为单位进行编译<br />
2.2、trace方式：以trace为单位进行编译；指能快速获取热路径的代码，从而采用更短的方式编译</li>
</ol>]]></description>
    <pubDate>Sun, 05 Jan 2025 09:57:57 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-56.html</guid>
</item>
<item>
    <title>安卓逆向 - 使用ApkTool进行逆向</title>
    <link>https://fuckdog.org/post-55.html</link>
    <description><![CDATA[<h1>ApkTool简介</h1>
<p>APKTool 是一种常用的逆向工程工具，专门用于处理 Android APK 文件（Android应用安装包）。它的主要功能是将 APK 文件进行反编译（Decompile）和重新打包（Recompile），以便开发者或研究人员分析和修改 Android 应用程序。</p>
<h1>ApkTool工作原理</h1>
<ol>
<li>
<p>解包过程：</p>
<ul>
<li>APKTool 使用 aapt（Android Asset Packaging Tool）解析 APK 的资源和 XML 文件。</li>
<li>将 APK 文件的内容分解为：</li>
<li>AndroidManifest.xml：包含应用的基本配置信息。</li>
<li>res/：应用的界面资源（如图片、布局文件）。</li>
<li>smali/：反编译后的代码。</li>
</ul>
</li>
<li>
<p>重新打包过程：</p>
<ul>
<li>APKTool 会将修改后的资源和代码重新组合为 APK 文件，保持原始结构。</li>
</ul>
</li>
</ol>
<h1>ApkTool常用命令</h1>
<pre><code>apktool --version （查看版本）
apktool d app.apk -o output_folder （反编译APK ：d表示解包）
apktool b output_folder -o new_app.apk (重新打包apk: b 表示打包)
apktool if app.apk (仅解包资源文件: 用于解包需要特定资源框架的应用)
apktool d app.apk -p custom_framework_path （指定系统框架: 对于依赖特定系统框架的应用，必须先安装相关框架文件）
apktool d app.apk --force （强制解包: 如果同名目录已经存在，默认情况下 APKTool 会提示错误，使用此选项可以覆盖输出）
apktool b output_folder --ignore-res （忽略错误继续打包: 用于资源文件可能损坏但希望完成打包的场景）
apktool b output_folder --copy-original （添加自定义配置: 用于保留未修改的资源文件或代码部分）
apktool b output_folder --use-aapt2 （自定义压缩选项: 适用于需要支持新版 Android 特性的场景）
apktool clean (清除缓存)</code></pre>
<h1>ApkTool解包后目录解析</h1>
<ul>
<li>assets(存放应用的静态资源文件,这里的文件通常是开发者自己添加的静态资源)</li>
<li>lib (存放应用的本地库文件,以 .so 结尾的文件，通常用 C/C++ 编写并通过 JNI 调用,按照 CPU 架构分为不同文件夹)</li>
<li>META-INF (存放签名相关的信息,MANIFEST.MF：描述文件内容的元数据,CERT.RSA 和 CERT.SF：APK 的数字签名和校验文件)</li>
<li>original (通常存放反编译过程中的临时文件，或者一些特殊框架信息, 如果为空，说明当前解包的 APK 不依赖额外框架)</li>
<li>res (存放资源文件（资源文件夹）,)</li>
<li>smali (存放反编译后的代码,每个类一个 .smali 文件，文件内容是 Android 的中间语言)</li>
<li>unknown (存放 APKTool 无法识别的文件或特殊资源)</li>
<li>AndroidManifest.xml (应用的核心配置文件,应用的包名、权限、组件（Activity、Service、Receiver 等）,启动 Activity、权限声明、Intent 过滤器)</li>
<li>apktool.yml (APKTool 的配置文件,记录解包过程中的元信息，例如版本号、框架依赖,配置项用于控制重新打包时的行为)</li>
</ul>
<h1>实战</h1>
<p>编写app，功能是需要邀请码才能进行注册,代码参考《Android 软件安全权威指南》<br />
要点:要编译release的，不然代码不全<br />
<a href="https://fuckdog.org/content/uploadfile/202412/546d1735473312.png"><img src="https://fuckdog.org/content/uploadfile/202412/thum-546d1735473312.png" alt="" /></a><br />
对apk进行解包<br />
<a href="https://fuckdog.org/content/uploadfile/202412/c1781735473501.png"><img src="https://fuckdog.org/content/uploadfile/202412/thum-c1781735473501.png" alt="" /></a><br />
查看解包后的文件<br />
<a href="https://fuckdog.org/content/uploadfile/202412/cd851735473554.png"><img src="https://fuckdog.org/content/uploadfile/202412/thum-cd851735473554.png" alt="" /></a><br />
随便输入注册信息，提示 Registration failed!<br />
<a href="https://fuckdog.org/content/uploadfile/202412/f8c61735473703.png"><img src="https://fuckdog.org/content/uploadfile/202412/thum-f8c61735473703.png" alt="" /></a><br />
分析smail语法，在MainActivity$1.smali 中</p>
<pre><code class="language-c">.class Lcom/example/crame0201/MainActivity$1;
.super Ljava/lang/Object;
.source "MainActivity.java"

# interfaces
.implements Landroid/view/View$OnClickListener;

# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
    value = Lcom/example/crame0201/MainActivity;-&gt;onCreate(Landroid/os/Bundle;)V
.end annotation

.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0x0
    name = null
.end annotation

# instance fields
.field final synthetic this$0:Lcom/example/crame0201/MainActivity;

# direct methods
.method constructor &lt;init&gt;(Lcom/example/crame0201/MainActivity;)V
    .locals 0
    .annotation system Ldalvik/annotation/MethodParameters;
        accessFlags = {
            0x8010
        }
        names = {
            null
        }
    .end annotation

    .line 70
    iput-object p1, p0, Lcom/example/crame0201/MainActivity$1;-&gt;this$0:Lcom/example/crame0201/MainActivity;

    invoke-direct {p0}, Ljava/lang/Object;-&gt;&lt;init&gt;()V

    return-void
.end method

# virtual methods
.method public onClick(Landroid/view/View;)V
    .locals 2

    .line 73
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;-&gt;this$0:Lcom/example/crame0201/MainActivity;

    invoke-static {p1}, Lcom/example/crame0201/MainActivity;-&gt;-$$Nest$fgetedit_userName(Lcom/example/crame0201/MainActivity;)Landroid/widget/EditText;

    move-result-object v0

    invoke-virtual {v0}, Landroid/widget/EditText;-&gt;getText()Landroid/text/Editable;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/Object;-&gt;toString()Ljava/lang/String;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/String;-&gt;trim()Ljava/lang/String;

    move-result-object v0

    iget-object v1, p0, Lcom/example/crame0201/MainActivity$1;-&gt;this$0:Lcom/example/crame0201/MainActivity;

    invoke-static {v1}, Lcom/example/crame0201/MainActivity;-&gt;-$$Nest$fgetedit_sn(Lcom/example/crame0201/MainActivity;)Landroid/widget/EditText;

    move-result-object v1

    .line 74
    invoke-virtual {v1}, Landroid/widget/EditText;-&gt;getText()Landroid/text/Editable;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/Object;-&gt;toString()Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/String;-&gt;trim()Ljava/lang/String;

    move-result-object v1

    .line 73
    invoke-static {p1, v0, v1}, Lcom/example/crame0201/MainActivity;-&gt;-$$Nest$mcheckSN(Lcom/example/crame0201/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z

    move-result p1

    const/4 v0, 0x0

    if-eqz p1, :cond_0

    .line 75
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;-&gt;this$0:Lcom/example/crame0201/MainActivity;

    sget v1, Lcom/example/crame0201/R$string;-&gt;unsuccessed:I

    invoke-static {p1, v1, v0}, Landroid/widget/Toast;-&gt;makeText(Landroid/content/Context;II)Landroid/widget/Toast;

    move-result-object p1

    .line 76
    invoke-virtual {p1}, Landroid/widget/Toast;-&gt;show()V

    goto :goto_0

    .line 78
    :cond_0
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;-&gt;this$0:Lcom/example/crame0201/MainActivity;

    sget v1, Lcom/example/crame0201/R$string;-&gt;successed:I

    invoke-static {p1, v1, v0}, Landroid/widget/Toast;-&gt;makeText(Landroid/content/Context;II)Landroid/widget/Toast;

    move-result-object p1

    .line 79
    invoke-virtual {p1}, Landroid/widget/Toast;-&gt;show()V

    .line 80
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;-&gt;this$0:Lcom/example/crame0201/MainActivity;

    invoke-static {p1}, Lcom/example/crame0201/MainActivity;-&gt;-$$Nest$fgetbtn_register(Lcom/example/crame0201/MainActivity;)Landroid/widget/Button;

    move-result-object p1

    invoke-virtual {p1, v0}, Landroid/widget/Button;-&gt;setEnabled(Z)V

    .line 81
    iget-object p1, p0, Lcom/example/crame0201/MainActivity$1;-&gt;this$0:Lcom/example/crame0201/MainActivity;

    sget v0, Lcom/example/crame0201/R$string;-&gt;register:I

    invoke-virtual {p1, v0}, Lcom/example/crame0201/MainActivity;-&gt;setTitle(I)V

    :goto_0
    return-void
.end method
</code></pre>
<p>核心的验证逻辑在下面的这段代码中<br />
1、作用：调用 checkSN 方法。<br />
2、参数说明：<br />
p1：可能是当前 Activity 的实例。<br />
v0：第一个字符串（通常是用户名，edit_userName 的值）。<br />
v1：第二个字符串（通常是注册码，edit_sn 的值）。<br />
返回值：该方法返回一个布尔值（true 或 false），结果被存储在寄存器 p1 中。</p>
<pre><code class="language-c">invoke-static {p1, v0, v1}, Lcom/example/crame0201/MainActivity;-&gt;-$$Nest$mcheckSN(Lcom/example/crame0201/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z

move-result p1

if-nez p1, :cond_0</code></pre>
<p>所以修改的就是 <code>if-nez p1, :cond_0</code> 这个逻辑，让它为false</p>
<pre><code class="language-c">if-eqz p1, :cond_0</code></pre>
<p>重新打包apk，并使用编写apk时生成的as签名加上<br />
java -jar apktool_2.10.0.jar b app-release （打包）<br />
apksigner sign --ks keys_stores.jks --out a.apk app-release.apk （加上签名，要输入签名时填入的密码）</p>
<p>成功注册<br />
<a href="https://fuckdog.org/content/uploadfile/202501/85901735733991.png"><img src="https://fuckdog.org/content/uploadfile/202501/thum-85901735733991.png" alt="" /></a></p>]]></description>
    <pubDate>Sun, 29 Dec 2024 10:18:39 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-55.html</guid>
</item>
<item>
    <title>Rust进阶 - 用枚举创建有意义的数</title>
    <link>https://fuckdog.org/post-54.html</link>
    <description><![CDATA[<h1>枚举解释</h1>
<p>Rust中的枚举可以封装多个选择，不过它们表现得与常规结构很相似:</p>
<pre><code>- 它们可以有trait和函数的impl块。
- 匿名和命名属性可以有不同的值。</code></pre>
<h1>实现过程</h1>
<pre><code class="language-Rust">// 使用枚举创建有意义的数
use std::io;

// 声明一个包含一些数据的枚举
// Rust的enum可以有任何值，而不仅仅是数值，可以有命名属性
pub enum ApplicationError{
    Code{full:usize,short:u16},
    Message(String),
    IOWrapper(io::Error),
    Unknow
}

// 实现一个简单的函数
impl ApplicationError{
    pub fn print_kind(&amp;self, mut to:&amp;mut impl io::Write) -&gt; io::Result&lt;()&gt;{
        let kind = match self{
            ApplicationError::Code{full:_ , short:_} =&gt; "Code",
            ApplicationError::Unknow =&gt; "Unknown",
            ApplicationError::IOWrapper(_) =&gt; "IOWrapper",
            ApplicationError::Message(_) =&gt; "Message"
        };
        write!(&amp;mut to ,"{}", kind)?;
        Ok(())
    }
}

// 对enum进行处理
pub fn do_work(choice:i32) -&gt; Result&lt;(), ApplicationError&gt; {
    if choice &lt; -100{
        Err(ApplicationError::IOWrapper(io::Error::from(io::ErrorKind::Other)))
    }else if choice==42{
        Err(ApplicationError::Code { full: choice as usize, short: (choice % u16::max_value() as i32) as u16 })
    }else if choice &gt; 42 {
        Err(ApplicationError::Message(format!("{} lead to a terrible error",choice)))
    }else {
        Err(ApplicationError::Unknow)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io;

    #[test]
    fn test_do_work(){
        let choice = 10;
        if let Err(error) = do_work(choice) {
            match error {
                ApplicationError::Code { full:code, short:_ } =&gt; 
                assert_eq!(choice as usize ,code),
                ApplicationError::Unknow|
                ApplicationError::IOWrapper(_) =&gt; assert!(choice &lt; 42),
                ApplicationError::Message(msg) =&gt;
                assert_eq!(format!(
                    "{} lead to a terrible error",choice
                ),msg)
            }
        }
    }

    #[test]
    fn test_application_error_get_kind(){
        let mut target = vec![];
        let _ = ApplicationError::Code { full: 100, short: 100 }.print_kind(&amp;mut target);
        assert_eq!(String::from_utf8(target).unwrap(),"Code".to_string());

        let mut target = vec![];
        let _ = ApplicationError::Message("0".to_string()).print_kind(&amp;mut target);
        assert_eq!(String::from_utf8(target).unwrap(),"Message".to_string());

        let mut target = vec![];
        let _ = ApplicationError::Unknow.print_kind(&amp;mut target);
        assert_eq!(String::from_utf8(target).unwrap(),"Unknown".to_string());

        let mut target = vec![];
        let error = io::Error::from(io::ErrorKind::WriteZero);
        let _ = ApplicationError::IOWrapper(error).print_kind(&amp;mut target);
        assert_eq!(String::from_utf8(target).unwrap(),"IOWrapper".to_string());
    }
}
</code></pre>]]></description>
    <pubDate>Tue, 24 Dec 2024 20:17:03 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-54.html</guid>
</item>
<item>
    <title>Rust - String</title>
    <link>https://fuckdog.org/post-53.html</link>
    <description><![CDATA[<h1>字符串</h1>
<p>1、字符串就是基于字节byte的集合</p>
<p>2、能将byte解析为文本</p>
<h2>1、字符串是什么</h2>
<p>1、Rust的核心语言层面，只有一个字符串类型:字符串切片str(或&amp;str)</p>
<p>2、字符串切片:对存储在其他地方、UTF-8编码的字符串的引用<br />
-字符串字面值:存储在二进制文件中，也是字符串切片</p>
<h2>2、String类型</h2>
<p>1、来自标准库而不是核心语言</p>
<p>2、它是可增长、可修改、可拥有的</p>
<p>3、它采用的是UTF-8编码</p>
<h3>2.1、通常说的字符串是指</h3>
<p>1、String和&amp;str(字符串切片)这两种类型</p>
<h3>2.2、其他类型的字符串</h3>
<p>1、Rust的标准库还包含了很多其他的字符串类型，例如:OsString、OsStr、CString、CStr</p>
<ul>
<li>区别在于:string 和 str 后缀，string是拥有所有权，str后缀指可借用</li>
<li>可存储不同编码的文本或在内存中以不同的形式展现</li>
</ul>
<h2>3、创建一个新的字符串<code>(String)</code></h2>
<p>1、很多<code>Vec&lt;T&gt;</code>的操作都可用于String</p>
<p>2、通过<code>String::new()</code>函数创建</p>
<p>3、常用的创建方法:使用初始值来创建String</p>
<ul>
<li>
<p><code>to_string()</code> 方法，可用于实现Display trait的类型,包括字符串字面值</p>
<pre><code class="language-rust">
// 创建字符串
let mut s = String::new();
// 常用的方法 to_string转换
let s = "test"; // 现在是&amp;str类型
let news = s.to_string(); // 通过to_string转换，变成string类型
// 直接使用to_string
let s1 = "hello world".to_string(); // 这样直接是string类型
// 使用string::from函数创建
let s2 = String::from("world hello");```</code></pre>
</li>
</ul>
<h2>4、更新String</h2>
<p>1、<code>pust_str()</code>方法:把一个字符串切片(&amp;strs)附加到string</p>
<ul>
<li>这个方法不会获取参数的所有权</li>
</ul>
<p>2、<code>pust()</code>方法:把单个字符附加到string</p>
<h2>5、拼接字符串</h2>
<p>1、+号连接</p>
<ul>
<li>前面的值是字符串，后面的值是字符串切片</li>
<li>后面如果要继续使用这两个值，前面的值已经不能使用，后面的值因为传的是引用，所以可以继续使用</li>
</ul>
<p>2、<code>format!</code> 宏:这个不会获取任何字符串的所有权</br></p>
<pre><code class="language-rust">
 let ss3 = format!("{}-{}",ss,ss1);
// 拼接字符串
 let ss = String::from("hello");
 let ss1 = String::from("world");
 //let add_ss = ss + &amp;ss1;
 //println!("{}",add_ss);
 // println!("ss的值:{}",ss); 不能使用这值
 println!("ss1的值:{}",ss1);

 // format!宏:连接多个字符串
 let ss3 = format!("{}-{}",ss,ss1);</code></pre>
<h2>6、为什么String类型不能通过索引表示？</h2>
<p>1、String类型是不支持通过索引的方式访问,因为string没有实现一个trait</p>
<h2>7、内部表示</h2>
<p>1、String类型，内部其实是<code>Vec&lt;u8&gt;</code>组成的</p>
<p>2、Rust有三种看待字符串的方式:</p>
<ul>
<li>字节 .byte方法</li>
<li>标量值 .chars方法</li>
<li>字形簇 第三方库</li>
</ul>
<p>3、Rust不允许对String进行索引的最后一个原因</p>
<ul>
<li>索引操作消耗一个常量时间</li>
<li>而String无法保证:需要遍历所有内容，来确定有多少个合法的字符。</li>
</ul>]]></description>
    <pubDate>Sun, 16 Jun 2024 17:08:00 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-53.html</guid>
</item>
<item>
    <title>Rust - HashMap</title>
    <link>https://fuckdog.org/post-52.html</link>
    <description><![CDATA[<h1>HashMap</h1>
<p>1、键值对的形式存储数据，一个键(key)对应一个值(value)</p>
<p>2、通过Hash函数:决定如何在内存中存放K和V</p>
<ul>
<li>适用场景:通过K(任何类型)来寻找数据,而不是索引</li>
</ul>
<h2>1、创建HashMap</h2>
<p>1、创建空HashMap:new() 函数</p>
<p>2、添加数据:insert() 方法</p>
<pre><code class="language-rust">
use std::collections::HashMap;

fn main() {
    // 创建hashmap
    // 如果只创建，不插入数据的话，会报错，要指定数据类型才不会报错
    //let mut scores:HashMap&lt;String, i32&gt; = HashMap::new();
    let mut scores = HashMap::new();
    // 插入数据
    scores.insert(String::from("年龄"), 10);
}</code></pre>
<h2>2、HashMap注意事项</h2>
<p>1、HashMap用的比较少,不在Prelude中</p>
<p>2、标准库对其支持比较少，没有内置的宏来创建HashMap</p>
<p>3、其数据是存放在heap上</p>
<p>4、是同构的,一个HashMap中</p>
<ul>
<li>所有的k的类型必须一致</li>
<li>所有的v的类型必须一致</li>
</ul>
<h2>3、另一种创建HashMap的方式:collect方法</h2>
<p>1、在元素类型为Tuple(元组)的Vector上使用collect方法，可以组建一个HashMaps</p>
<ul>
<li>要求元组有两个值:一个作为K，一个作为V</li>
<li>collect方法可以把数据整合成多种集合类型，包括HashMap</li>
<li>返回值需要显式的指明类型</li>
</ul>
<h2>4、HashMap的所有权</h2>
<p>1、对于实现了Copy trait的类型(如i32),值都会被复制到HashMap中</p>
<p>2、对于拥有所有权的值(如string),值会被移动，所有权会被转移到HashMap中</p>
<pre><code class="language-rust">    let hello_hash = String::from("hello");
        let world_hash = String::from("world");
        let mut all_hash = HashMap::new();
        all_hash.insert(hello_hash, world_hash);
        // println!("{},{}",hello_hash,world_hash); 报错</code></pre>
<h2>5、访问HashMap的值</h2>
<p>1、通过get()方法</p>
<pre><code class="language-rust">// 访问HashMap的值
    let get_hello = String::from("hello");
    let jg = all_hash.get(&amp;get_hello);
    match jg {
        Some(jg) =&gt; println!("{}",jg),
        None =&gt; println!("空")
    }</code></pre>
<h2>6、遍历HashMap</h2>
<p>1、通过for循环</p>
<pre><code class="language-rust">// 遍历HashMap
    for (k,v) in &amp;all_hash{
        println!("{}:{}",k,v);
    }</code></pre>
<h2>7、更新HashMap</h2>
<p>1、HashMap的大小可变</p>
<p>2、每个K同时只能对应一个V</p>
<p>3、更新HashMap中的数据</p>
<ul>
<li>K 存在，需要对V进行修改
<ul>
<li>替换现有的V</li>
<li>保留现有的v，忽略新的v</li>
<li>合并现有的v和新的v</li>
</ul></li>
<li>K不存在
<ul>
<li>添加新的k,v</li>
</ul></li>
</ul>
<p>情况一、替换现有的v</p>
<ul>
<li>如果插入的k相同，v不同，那么后面的v的值会覆盖前面的值</li>
</ul>
<p>情况二、只有K不对应任何值的情况下，才插入v</p>
<ul>
<li>使用entry方法，判断k中是否有值,没有就使用or_insert方法插入</li>
<li>两个方法返回的都是引用</li>
</ul>
<pre><code class="language-rust">  // 更新hashmap
    // 情况一
    all_hash.insert(String::from("name"),String::from("小明"));
    all_hash.insert(String::from("name"),String::from("小宏"));
    println!("{:?}",all_hash);

    // 情况二
    // 插入一个新k，如果k中没有值，就添加一个值
    all_hash.entry(String::from("old")).or_insert(String::from("30"));</code></pre>]]></description>
    <pubDate>Sun, 16 Jun 2024 17:02:00 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-52.html</guid>
</item>
<item>
    <title>Rust - panic!</title>
    <link>https://fuckdog.org/post-51.html</link>
    <description><![CDATA[<h1>不可恢复的错误与panic!</h1>
<p>1、Rust的错误处理</p>
<ul>
<li>Rust的可靠性:错误处理</li>
</ul>
<p>2、错误的分类</p>
<ul>
<li>可恢复 : 如文件未找到</li>
<li>不可恢复 : bug, 如数组越界</li>
</ul>
<p>3、Rust没有其他语言的异常的机制</p>
<ul>
<li>对于可恢复的错误的方法: Result&lt;T,E&gt;</li>
<li>对不可恢复的错误的方法: panic!宏</li>
</ul>
<h2>1、不可恢复错误</h2>
<p>1、当panic!宏执行</p>
<ul>
<li>程序会打印一个错误信息</li>
<li>展开 、 清理调用栈</li>
<li>退出程序</li>
</ul>
<p>2、为了应对展开和清理调用栈</p>
<ul>
<li>默认情况下当panic发生
<ul>
<li>Rust沿着调用栈往会走</li>
<li>清理每个遇到函数中的数据</li>
</ul></li>
<li>或者可以立即中止调用栈
<ul>
<li>不进行清理，直接停止程序</li>
<li>后续需要OS去清理内存</li>
</ul></li>
</ul>
<p>3、想让二进制文件更小，就可以把展开改成中止</p>
<ul>
<li>通过修改cargo.toml文件[profile.release]
<ul>
<li>panic='about'</li>
</ul></li>
</ul>
<h2>2、panic!宏的使用</h2>
<p>1、panic!可能出现的地方</p>
<ul>
<li>自己编写代码中</li>
<li>调用的依赖的代码中</li>
</ul>
<p>2、可以通过panic!宏来回溯出现问题的代码</p>
<p>3、可以通过环境变量设置调试信息</p>
<p>4、在调试的时候，cargo run 必须不能使用--release</p>
<pre><code class="language-rust">fn main() {
    panic!("出现错误了");
}</code></pre>
<h2>3、可恢复的错误</h2>
<p>1、Result&lt;T,E&gt;枚举</p>
<pre><code class="language-rust">enum Result&lt;T,E&gt;{
    Ok(T),
    Err(E),
}
// T : 表示操作成功的情况，OK变体里返回的数据的类型
// E : 表示操作失败的情况，Err变体里返回的错误的类型</code></pre>
<pre><code class="language-rust">use std::fs::File;
fn main() {
    // 这个返回的值就是Resule枚举类型
    let f = File::open("1.txt");
}</code></pre>
<h2>4、处理Result枚举的结果，使用match</h2>
<pre><code class="language-rust">use std::fs::File;

fn main() {
    // 这个返回的值就是Resule枚举类型
    let f = File::open("1.txt");

    // 处理结果
    match f {
        Ok(file) =&gt; file,
        Err(err) =&gt; panic!(
            "打开文件失败{:?}",{err})
    };
}</code></pre>
<ul>
<li>匹配不同的错误使用kind()方法，但是里面会嵌套多个match,由此引出了unwrap</li>
<li>unwrap : match表达式的一个快捷方法
<pre><code class="language-rust">let f = File::open("1.txt").unwrap();</code></pre></li>
<li>但是unwrap不能自定义错误信息，所以引出了另一个方法expect
<pre><code class="language-rust">let f = File::open("1.txt").expect("无法打开文件");</code></pre></li>
</ul>
<h2>5、传播错误</h2>
<p>？运算符:用来传播错误的一种快捷方式</p>
<ul>
<li>后面加？直接就类似于执行了match里面的操作</li>
<li>如果result是OK，OK中的值就是表达式的结果，继续执行程序</li>
<li>如果result是Err, Err就是整个函数的返回值，就像使用了return</li>
<li>?运算符只能作用返回结果为Result才可以使用
<pre><code class="language-rust">fn read_username_from_file() -&gt; Result&lt;String,io::Error&gt;{
// 打开文件 后面加？直接就类似于执行了match里面的操作
let mut f = File::open("1.txt")?;
// 创建一个字符串
let mut s = String::new();
f.read_to_string(&amp;mut s)?;
Ok(s)
}</code></pre></li>
</ul>
<h2>6、总体原则</h2>
<p>1、在定义一个可能失败的函数的时候，优先考虑返回的是Result<br />
2、否则就panic!</p>]]></description>
    <pubDate>Sun, 16 Jun 2024 16:57:00 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-51.html</guid>
</item>
<item>
    <title>Rust - Trait</title>
    <link>https://fuckdog.org/post-50.html</link>
    <description><![CDATA[<h1>泛型</h1>
<p>1、泛型:提高代码复用能力</p>
<ul>
<li>处理重复代码的问题</li>
</ul>
<p>2、泛型是具体类型或其他属性的抽象代替</p>
<ul>
<li>你编写的代码不是最终的代码，而是一种模板，里面有一些&quot;占位符&quot;</li>
<li>编译器在编译时将&quot;占位符&quot;替换为具体的类型
<ul>
<li>例如: fn targest<T>(list:&amp;[T]) -&gt; T{}</li>
<li>里面的T都叫为类型参数:</li>
</ul></li>
<li>类型参数很短，通常一个字母</li>
</ul>
<h2>1、函数定义中的泛型</h2>
<pre><code class="language-rust">// 函数中泛型
fn get_list&lt;T&gt;(list:&amp;[T]) -&gt; T{
    let mut largest = list[0];
    for &amp;item in list{
        if item &gt; largest {
            largest = item;
        }
    }
    largest
}
fn main() {
    let number = [1,23,4,5,6,323,232];
    let result = get_list(&amp;number);
}</code></pre>
<h2>2、Struct中定义泛型</h2>
<pre><code class="language-rust">struct Point&lt;T&gt;{
    x:T,
    y:T,
}</code></pre>
<h2>3、方法定义中的泛型</h2>
<p>1、为struct或enum实现方法的时候，可在定义中使用泛型</p>
<pre><code class="language-rust">struct Point&lt;T&gt;{
    x:T,
    y:T,
}
// 方法中定义泛型
impl&lt;T&gt; Point&lt;T&gt; {
    fn x(&amp;self) -&gt;&amp;T {
        &amp;self.x
    }
}</code></pre>
<h2>4、trait</h2>
<p>1、trait会告诉Rust编译器</p>
<ul>
<li>某种类型具有哪些并且可以与其他类型共享的功能</li>
</ul>
<p>2、trait:抽象的定义共享功能</p>
<p>3、trait约束:泛型类型参数指定为实现了特定行为的类型</p>
<h2>5、定义一个Trait</h2>
<p>1、trait的定义:把方法签名放一起，从而来定义实现某种目的所必需的一组行为</p>
<ul>
<li>关键字:trait</li>
</ul>
<p>2、只定义方法签名，不提供具体实现</p>
<p>3、可以定义多个方法，每个方法后面使用;</p>
<p>4、实现了该Trait就必须把里面的方法实现</p>
<h3>5.1、练习</h3>
<pre><code class="language-rust">// 代码:main.rs
use trait_test::{NewsArticle, Summary};
fn main() {
    //let number = [1,23,4,5,6,323,232];
    //let result = get_list(&amp;number);
    let s = NewsArticle{
        headline:String::from("a"),
        location:String::from("b"),
        author:String::from("c"),
        content:String::from("d"),
    };

    println!("{}",s.summarize());
}</code></pre>
<pre><code class="language-rust">// 代码:lib.rs
// 实现一个trait
pub trait Summary {
    fn summarize(&amp;self) -&gt; String;
}

// 定义结构体
pub struct NewsArticle{
    pub headline:String,
    pub location:String,
    pub author:String,
    pub content:String,
}

// 实现这个trait
impl Summary for NewsArticle {
    fn summarize(&amp;self) -&gt; String{
        format!("{},{},{}",self.headline,self.location,self.author)
    }
}</code></pre>
<h2>6、实现trait的约束</h2>
<p>1、可以在某个类型上实现某个trait的前提条件是：</p>
<ul>
<li>这个类型或这个trait是在本地crate里定义的</li>
</ul>
<p>2、无法为外部类型来实现外部的trait:</p>
<ul>
<li>这个限制是程序属性的一部分(也就是一致性)</li>
<li>更具体地说是孤儿规则:之所以这样命名是因为父类型不存在</li>
</ul>
<h2>7、默认实现</h2>
<p>1、可以在trait中直接实现了方法，</p>
<p>2、重写:后面调用者不想用默认实现，可以重写该方法</p>
<pre><code class="language-rust">pub trait Summary {
    fn summarize(&amp;self) -&gt; String{
        String::from("默认实现")
    }
}</code></pre>
<p>3、默认实现的Trait可以调用Trait的其他方法，即使这个方法没有实现</p>
<pre><code class="language-rust">pub trait Summary {
    fn summarize(&amp;self) -&gt; String{
        format!("(调用trait中的get_string方法{})",self.get_string())
    }
    fn get_string(&amp;self) -&gt; String;
}

// 定义结构体
pub struct NewsArticle{
    pub headline:String,
    pub location:String,
    pub author:String,
    pub content:String,
}

// 实现这个trait
impl Summary for NewsArticle {
     fn get_string(&amp;self) -&gt; String{
         format!("@{}",self.author)
     }
}</code></pre>
<h2>8、Trait作为参数</h2>
<p>1、impl Trait语法:适用于简单情况</p>
<p>2、Trait bound 语法: 可用于复杂情况</p>
<ul>
<li>impl trait 语法是trait bound的语法糖</li>
</ul>
<p>3、使用+ 指定多个Trait bound</p>
<p>4、Trait bound 使用where子句</p>
<ul>
<li>在方法签名后指定where子句</li>
</ul>]]></description>
    <pubDate>Sun, 16 Jun 2024 16:50:00 +0800</pubDate>
    <dc:creator>ljierui</dc:creator>
    <guid>https://fuckdog.org/post-50.html</guid>
</item></channel>
</rss>