Jekyll2023-12-20T14:20:39+00:00https://chinsyo.com/feed.xml晨晓 | Chinsyo王晨晓的博客 | 开发工程师,关注数据、设计、效率宏定义的黑魔法,C语言模拟命名参数2020-10-23T12:00:00+00:002020-10-23T12:00:00+00:00https://chinsyo.com/2020/10/23/macro-magic-named-parameter<p>今天翻阅代码时偶然看到函数调用传入命名参数,初看以为是C语言新标准的某个语法,探究了一下源码。PoC代码如下:</p>
<p><img src="https://cdn.chinsyo.com/img/macro-magic-named-parameter/01.png" alt="" /></p>
<p>代码很简单无需展开探讨,主要思路为通过宏定义将命名参数构造为预先定义好的结构体,留意几个细节即可。</p>
<ol>
<li>模拟实现的命名参数支持默认参数,这是由C语言结构体自身语法支持的</li>
<li>定义和已知函数同名的宏定义会发生覆盖,因此宏定义要放在函数声明之后</li>
<li>为了支持多文件共同参与编译的开发场景,因此要在函数声明前撤销宏定义</li>
</ol>
<p>Readability is reliability, 可读性即可靠性。C语言作为上古遗物,简洁但完备的语法,兼顾了开发效率和运行效率,同时和操作系统结合紧密,时至今日仍然焕发着蓬勃的生机。</p>Chinsyo今天翻阅代码时偶然看到函数调用传入命名参数,初看以为是C语言新标准的某个语法,探究了一下源码。PoC代码如下:宏定义的黑魔法,C语言模拟函数重载2020-05-06T12:00:00+00:002020-05-06T12:00:00+00:00https://chinsyo.com/2020/05/06/macro-magic-overload<p>重载(Overload)是编程语言中具有的一项特性,这项特性允许创建数项名称相同但输入类型或个数不同的子程序(少数语言支持的重载函数返回类型不在本文讨论范围)。此特性很容易和面向对象编程中重写(Override)混淆,理解相关概念时需要有所区分。</p>
<p>不难理解,对于以下两种形式的重载,C语言源文件无法通过编译。</p>
<p>1)名称相同,但参数个数不同</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">function</span><span class="p">(</span><span class="kt">int</span> <span class="n">arg1</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">function</span><span class="p">(</span><span class="kt">int</span> <span class="n">arg1</span><span class="p">,</span> <span class="kt">int</span> <span class="n">arg2</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>2)名称相同,但参数类型不同</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">function</span><span class="p">(</span><span class="kt">int</span> <span class="n">arg1</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">function</span><span class="p">(</span><span class="kt">char</span> <span class="n">arg1</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>第一种情况可以用于模拟高级编程语言的默认参数,是本文讨论的重点。宏定义的展开和替换作用于预编译阶段,可以对常量、类型、代码块以及函数名设置别名。</p>
<p>既然函数名不可以相同,那么有没有可能通过以下方式模拟重载呢?</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">function1</span><span class="p">(</span><span class="kt">int</span> <span class="n">arg1</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">function2</span><span class="p">(</span><span class="kt">int</span> <span class="n">arg1</span><span class="p">,</span> <span class="kt">int</span> <span class="n">arg2</span><span class="p">);</span>
<span class="cp">#define FUNCTION function1
#define FUNCTION function2
</span></pre></td></tr></tbody></table></code></pre></div></div>
<p>显然这样的方式并不奏效,重复的宏定义会覆盖先前的内容。经过一番搜索查阅到以下方式并验证有效。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="cp">#define GET_MACRO(_1,_2,_3,NAME,...) NAME
#define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2, FOO1)(__VA_ARGS__)
</span><span class="kt">int</span> <span class="nf">FOO1</span><span class="p">(</span><span class="kt">int</span> <span class="n">arg1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">arg1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">FOO2</span><span class="p">(</span><span class="kt">int</span> <span class="n">arg1</span><span class="p">,</span> <span class="kt">int</span> <span class="n">arg2</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">+</span> <span class="n">arg2</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">FOO3</span><span class="p">(</span><span class="kt">int</span> <span class="n">arg1</span><span class="p">,</span> <span class="kt">int</span> <span class="n">arg2</span><span class="p">,</span> <span class="kt">int</span> <span class="n">arg3</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">arg1</span> <span class="o">+</span> <span class="n">arg2</span> <span class="o">+</span> <span class="n">arg3</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>GCC编译器可以通过-E参数单独进行预编译检查效果,如下。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>FOO<span class="o">(</span>1<span class="o">)</span> <span class="o">=></span> FOO1<span class="o">(</span>1<span class="o">)</span> <span class="o">=></span> 1
FOO<span class="o">(</span>1,2<span class="o">)</span> <span class="o">=></span> FOO2<span class="o">(</span>1,2<span class="o">)</span> <span class="o">=></span> 3
FOO<span class="o">(</span>1,2,3<span class="o">)</span> <span class="o">=></span> FOO3<span class="o">(</span>1,2,3<span class="o">)</span> <span class="o">=></span> 6
</pre></td></tr></tbody></table></code></pre></div></div>
<p>以三个入参数为例,FOO(1,2,3)首先会替换为GET_MACRO(1,2,3,FOO3,FOO2,FOO1)(1,2,3)。紧接着GET_MACRO(1,2,3,FOO3,FOO2,FOO1)会替换为第3个(从0数起)参数,即FOO3。</p>
<p>FOO的宏定义中,__VA_ARGS__为空时参数会以逗号开始,在C语言中不是有效的语法,因此该方案模拟的重载不支持0个参数的情况。对于GCC编译器,stackoverflow高赞答案有利用##__VA_ARGS__这一拓展语法的适配方式,感兴趣的朋友可以前往查看。</p>
<p>以上虽然模拟了重载参数个数的效果,但存在多处硬编码显然不够优雅,因此很快有人基于google讨论组的方式实现了通用版本。</p>
<p><img src="https://cdn.chinsyo.com/img/macro-magic-overload/01.png" alt="" /></p>
<p>通过__NARG__(<strong>VA_ARGS</strong>)获取参数个数N,然后通过##这一特殊的宏定义和FUNC拼接得到FUNCN作为函数名。以FOO为例,该方案只需要以下一行代码即实现了对FOO传入1~63个参数时会自动关联到FOO1~FOO63。</p>
<p>63个参数限制,我猜测和平台寄存器个数相关,此处可以为不受限制,尽管函数命名都以数字结尾同样丧失了一定的灵活性,我们可以安慰自己「约定优于配置」嘛。😅</p>
<p>如果你的开发环境采用C99及以上标准,也可以通过以下方式实现,核心思想类似。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="rouge-code"><pre><span class="cp">#include <stdio.h>
#include <order/interpreter.h>
</span>
<span class="kt">void</span> <span class="nf">oneArg</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"one arg: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">a</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">twoArgs</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"two args: %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">threeArgs</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">,</span> <span class="kt">int</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"three args: %d %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>
<span class="cp">#define ORDER_PP_DEF_8function_list \
ORDER_PP_CONST(("unused") \
(oneArg) \
(twoArgs) \
(threeArgs))
</span>
<span class="cp">#define SelectFunction(...) ORDER_PP ( \
8seq_at(8tuple_size(8((__VA_ARGS__))), 8function_list) \
)
</span>
<span class="cp">#define Overloaded(...) SelectFunction(__VA_ARGS__)(__VA_ARGS__)
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Overloaded</span><span class="p">(</span><span class="mi">42</span><span class="p">);</span>
<span class="n">Overloaded</span><span class="p">(</span><span class="mi">42</span><span class="p">,</span> <span class="mi">47</span><span class="p">);</span>
<span class="n">Overloaded</span><span class="p">(</span><span class="mi">42</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">64</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>C11及以上标准(GCC 4.9起)添加了额外关键字_Generic,可以借助其实现对参数类型的重载,目前我没有相关需求故不再展开介绍。方案如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="n">foo_int</span> <span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">)</span>
<span class="n">foo_char</span> <span class="p">(</span><span class="kt">char</span> <span class="n">b</span><span class="p">)</span>
<span class="n">foo_float_int</span> <span class="p">(</span><span class="kt">float</span> <span class="n">c</span> <span class="p">,</span> <span class="kt">int</span> <span class="n">d</span><span class="p">)</span>
<span class="cp">#define foo(_1, ...) _Generic((_1), \
int: foo_int, \
char: foo_char, \
float: _Generic((FIRST(__VA_ARGS__,)), \
int: foo_float_int))(_1, __VA_ARGS__)
#define FIRST(A, ...) A
</span></pre></td></tr></tbody></table></code></pre></div></div>
<p>第一种方案对参数个数的重载巧妙借助了宏定义的灵活性,通过预编译期替换文本的方式实现了高级编程语言的特性,不得不感叹巧夺天工。</p>
<p>如果你想深入了解这些内容,不妨点击以下参考阅读进行深入学习。</p>
<p>参考阅读:</p>
<p>[1] https://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments</p>
<p>[2] https://stackoverflow.com/questions/479207/how-to-achieve-function-overloading-in-c</p>
<p>[3] https://stackoverflow.com/questions/22505633/static-if-in-c99s-preprocessor/22624852#22624852</p>
<p>[4] https://gustedt.wordpress.com/2010/06/03/default-arguments-for-c99/</p>Chinsyo重载(Overload)是编程语言中具有的一项特性,这项特性允许创建数项名称相同但输入类型或个数不同的子程序(少数语言支持的重载函数返回类型不在本文讨论范围)。此特性很容易和面向对象编程中重写(Override)混淆,理解相关概念时需要有所区分。Git日志的一些使用技巧2020-01-01T12:00:00+00:002020-01-01T12:00:00+00:00https://chinsyo.com/2020/01/01/git-log-useful-tips<p>2020年1月1日,各位读者新年快乐!本次更新一些《Pro Git》中记录的日志小技巧。</p>
<h3 id="统计贡献者的代码提交次数">统计贡献者的代码提交次数</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git shortlog <span class="nt">-sne</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>shortlog是用于简化log输出的命令,可以方便的汇总展示不同提交者的提交日志。 参数含义如下:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nt">-s</span> 只输出提交次数,不输出相应提交的日志。
<span class="nt">-n</span> 按照提交次数排序,不指定该参数则按照贡献者字母顺序。
<span class="nt">-e</span> 同时显示提交者的email地址。
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="制作版本更新的简报">制作版本更新的简报</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git shortlog <span class="nt">--no-merges</span> BRANCH <span class="nt">--not</span> PREV_TAG
</pre></td></tr></tbody></table></code></pre></div></div>
<p>这个命令的作用为输出自指定tag之后该分支提交内容的简报。</p>
<h3 id="查看引用的改动记录">查看引用的改动记录</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git reflog show master@<span class="o">{</span>one.week.ago<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>和log的区别在于,reflog会展示所有造成引用变化的记录,比如rebase操作。</p>
<h3 id="查询分支的提交合并情况">查询分支的提交合并情况</h3>
<ul>
<li>查询experiment分支未合并到master分支的提交
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git log master..experiment
</pre></td></tr></tbody></table></code></pre></div> </div>
</li>
<li>查询本地未提交的记录
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git log origin/master..HEAD
</pre></td></tr></tbody></table></code></pre></div> </div>
<p>此处的HEAD可以省略,git使用HEAD代替空语法。</p>
</li>
<li>查询两个以上的分支提交合并情况
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git log feature1 feature2 <span class="nt">--not</span> develop
</pre></td></tr></tbody></table></code></pre></div> </div>
<p>以上命令的作用为检查没有同步合并到develop分支的feature1和feature2提交。</p>
</li>
<li>查询两个分支不共有的提交
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git log <span class="nt">--left-right</span> master...experiment
</pre></td></tr></tbody></table></code></pre></div> </div>
<p>–left-right会显示每个提交处于哪一侧的分支,对于发版前的检查非常有效。</p>
</li>
</ul>Chinsyo2020年1月1日,各位读者新年快乐!本次更新一些《Pro Git》中记录的日志小技巧。Git如何下载仓库子目录2019-11-24T12:00:00+00:002019-11-24T12:00:00+00:00https://chinsyo.com/2019/11/24/git-download-subdirectory<p>最近工作内容涉及加密算法日益变多,趁周末休息准备动手写几个OpenSSL 的项目加强学习,OpenSSL 项目很大,即使按照我在之前文章提过的方法 <code class="language-plaintext highlighter-rouge">git clone --depth 1</code> 或是下载压缩包也需要不少的时间和空间。</p>
<p>在查阅一些资料后了解到有几个方式可以实现只下载特定目录的效果,需要注意网上一些教程和问答的做法其实仍然是下载整个项目,但只 checkout 特定目录,这种方式并不能有效的改善下载时间和硬盘空间。</p>
<p>经过验证以下三种方式有效:</p>
<h3 id="通过第三方工具-gitzip-downgit-等">通过第三方工具 GitZip, DownGit 等</h3>
<p>搜索关键字即可找到相应的工具,前者有对应的 Chrome/Firefox 插件。</p>
<h3 id="对于-github-项目">对于 GitHub 项目</h3>
<p>GitHub 项目查看文件的时候路径中通常会有 tree/master 的部分,将这部分替换为 trunk 即为对应的 svn 地址,便可以通过 svn 命令导出或检出。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>svn <span class="nb">ls </span>https://github.com/openssl/openssl.git/trunk/demos
</pre></td></tr></tbody></table></code></pre></div></div>
<p>通过以上命令检查对应地址的文件是否正确,然后有两个选择:导出(export)或是检出(checkout),区别在于前者仅导出文件,后者保留 svn 提交记录。Git 项目的 svn 记录由 cvs2svn 自动生成,实测使用 export 更快。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>svn <span class="nb">export </span>https://github.com/openssl/openssl.git/trunk/demos
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="对于非-github-项目">对于非 GitHub 项目</h3>
<p>上面的方式经过验证在 Gitlab 无效,是 GitHub 的独有特性。对于非 GitHub 项目可以使用 git-archive 实现效果,经验证 GitHub 未开放 git-archive 权限。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>git archive <span class="nt">--verbose</span> <span class="nt">--format</span> <span class="nb">tar</span> <span class="nt">--remote</span> git@gitlab.com:chinsyo/sample.git HEAD subdir <span class="o">></span> subdir.tar
</pre></td></tr></tbody></table></code></pre></div></div>
<p>我几乎不会为了一个小功能安装图形化工具,也极少使用 GitHub 之外的代码网站,尤其是 GitHub 开放了免费的 private repo 之后。因此对我个人而言使用频率最高的方式依次是2、3、1,顺便吐槽一下 OpenSSL 项目大到 octotree 插件半天加载不出来项目结构。图片</p>
<p>希望以上内容对你有用。</p>Chinsyo最近工作内容涉及加密算法日益变多,趁周末休息准备动手写几个OpenSSL 的项目加强学习,OpenSSL 项目很大,即使按照我在之前文章提过的方法 git clone --depth 1 或是下载压缩包也需要不少的时间和空间。GitHub Pages域名劫持的诊断与抢救2019-08-21T12:00:00+00:002019-08-21T12:00:00+00:00https://chinsyo.com/2019/08/21/dns-record-troubleshoot<p>身体抱恙,请假休息。闲来无事打开 Google 检查下博客收录情况,日常查看 Search Console 数据收录页面大概 40 个左右。</p>
<p>今天发现搜索结果有些异常,在搜索栏输入<code class="language-plaintext highlighter-rouge">site:chinsyo.com</code>,居然足足有 8 页数据。</p>
<p><img src="https://cdn.chinsyo.com/img/dns-record-troubleshoot/01.jpeg" alt="" /></p>
<p>事出反常必有妖,当翻到第 3 页尾部的时候发现了端倪。我的域名怎么会出现俄文搜索结果,而且是一个我从未开放的子域名,莫非是恶意 SEO?</p>
<p>点击搜索结果,地址栏出现的确实是我的域名。</p>
<p><img src="https://cdn.chinsyo.com/img/dns-record-troubleshoot/02.jpeg" alt="" /></p>
<p>看来这次确实摊上事了,先确定一下这个子域名都劫持了什么内容,截图留证。同理在搜索栏输入<code class="language-plaintext highlighter-rouge">site:cahtong.chinsyo.com</code>。</p>
<p><img src="https://cdn.chinsyo.com/img/dns-record-troubleshoot/03.jpeg" alt="" /></p>
<p>大概浏览一遍搜索结果,还好没有违法信息,稍微缓和一口气。</p>
<p>这时回想整个事件,我的域名 2015 年注册,期间一直绑定在<code class="language-plaintext highlighter-rouge">name.com</code>账号下,接触到<code class="language-plaintext highlighter-rouge">namesilo.com</code>后发现价格有不小的优惠,于是转移到了<code class="language-plaintext highlighter-rouge">namesilo.com</code>账号下。那么会是转移后 DNS Server 设置错误吗?</p>
<p><img src="https://cdn.chinsyo.com/img/dns-record-troubleshoot/04.jpeg" alt="" /></p>
<p>急忙登录账号检查,并没有异常。难道是 DNS Pod 的记录被篡改了?鉴于腾讯的一贯尿性……</p>
<p><img src="https://cdn.chinsyo.com/img/dns-record-troubleshoot/05.jpeg" alt="" /></p>
<p>DNS 记录也是正常的,108~111 都是 GitHub 的 IP,这下犯难了,先查一下子域名的 DNS 记录再说。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>nslookup cahtong.chinsyo.com
Server: 192.168.50.1
Address: 192.168.50.1#53
Non-authoritative answer:
Name: cahtong.chinsyo.com
Address: 185.199.109.153
Name: cahtong.chinsyo.com
Address: 185.199.108.153
</pre></td></tr></tbody></table></code></pre></div></div>
<p>子域名的结果也指向 GitHub 的 IP,暂时没有定位到问题,Google 搜索 GitHub Pages 域名劫持没有找到有效信息。</p>
<p>回想整个流程发现 DNS 记录有两条泛解析指向 GitHub,而 GitHub Pages 是通过在仓库根目录设置 CNAME 文件绑定域名,整个流程是没有权限校验的。搜索 GitHub Pages 自定义域名官方也没有给出具体的设置方式,这和我的记忆有出入,记忆中 108~111 的 IP 地址是官方教程提供的。</p>
<p>第三方教程大多通过<code class="language-plaintext highlighter-rouge">CNAME记录</code>指向自己的<code class="language-plaintext highlighter-rouge">chinsyo.github.io</code>域名,而不是直接修改<code class="language-plaintext highlighter-rouge">A记录</code>指向 GitHub Pages 的 IP。在修改 DNS 记录的时候才发现同一类型 DNSPod 只支持添加两条记录,这也验证了我为什么添加了两条<code class="language-plaintext highlighter-rouge">@</code>的<code class="language-plaintext highlighter-rouge">A记录</code>之后仍然添加了两条<code class="language-plaintext highlighter-rouge">*</code>的<code class="language-plaintext highlighter-rouge">A记录</code>。</p>
<p>找到问题后解决就容易多了,首先删除目前的 DNS 记录并添加<code class="language-plaintext highlighter-rouge">chinsyo.github.io</code>的<code class="language-plaintext highlighter-rouge">CNAME记录</code>,顺便把当初自信 DNS 记录不会变才设置的 TTL 从 43200(12小时) 复原回 300(5分钟) 的默认值。</p>
<p>修改完成后由于 TTL 值很大所以不会立即生效,执行 <code class="language-plaintext highlighter-rouge">ping</code>, <code class="language-plaintext highlighter-rouge">nslookup</code>, <code class="language-plaintext highlighter-rouge">traceroute</code> 仍会看到缓存的结果。可以多尝试清除 DNS 记录后重试,清除方法如下。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span><span class="nb">sudo </span>dscacheutil <span class="nt">-flushcache</span><span class="p">;</span> <span class="nb">sudo </span>killall <span class="nt">-HUP</span> mDNSResponder<span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>大概半小时后再次访问<code class="language-plaintext highlighter-rouge">cahtong.chinsyo.com</code>已经无法解析,接下来需要从 Google 删除无关的搜索结果,避免博客被降权。中间有个小插曲,搜索了百度、必应、360相关引擎发现我的域名,包括被污染和未污染的子域名都没有收录。呃……也算帮我删除结果省事了吧……</p>
<p>一番搜索之后找到 Google 提供的删除页面。</p>
<p><img src="https://cdn.chinsyo.com/img/dns-record-troubleshoot/06.jpeg" alt="" /></p>
<p>同样的,这里也有一个小插曲。Google 提供了三种删除申请,分别是过期内容报告,违法内容投诉和站长主动删除。第一反应当然是站长主动删除,然而我只认证了根域名,而 Google 将根域名和子域名视为不同权限。为了避免违法内容影响到权重,并且现在被污染的子域名已经无法访问,只好退而求其次选择了过期内容报告。</p>
<p>截止这篇博客发布,申请仍然处于悲催的待定中。域名四年多的时间里使用错误的配置随我辗转<code class="language-plaintext highlighter-rouge">name.com</code>和<code class="language-plaintext highlighter-rouge">namesilo.com</code>,到头来是四年前的 DNS 配置错误,信息安全果然容不得半点马虎,另外千万不要轻信百度来的野生教程。</p>
<p>各位读者如果持有域名,也务必检查一下自己的 DNS 设置是否正确。躺着也中枪的腾讯这次是真的被冤枉了。</p>Chinsyo身体抱恙,请假休息。闲来无事打开 Google 检查下博客收录情况,日常查看 Search Console 数据收录页面大概 40 个左右。我的书单2019-08-18T12:00:00+00:002019-08-18T12:00:00+00:00https://chinsyo.com/2019/08/18/my-reading-list<p><img src="https://cdn.chinsyo.com/img/my-reading-list-1/01.jpg" alt="" /></p>
<p>在最近两个月遇到新的问题和挑战时,我花了很多时间甄别需要怎样的知识和技能去应对,然后不辞辛劳的收集资料和书单。诚然,买书和读书是两件相关性并不强的独立事件,我一度难以理解正版图书、音乐、电影和软件的付费习惯都没有养成的消费市场为什么在知识付费面前表现出空前的积极性。直到后来读到一句话「人们付费的不是习得知识这个结果,而是付费这个动作带给他们习得知识的错觉」,大意如此。</p>
<p>整理书单才发现近两年人文社科等和本职工作无关的类目极少阅读,技术类书籍版次更新频繁,豆瓣书评往往样本不足,发出来书单供大家参考。</p>
<p>首先是两本和编程无关的设计书籍。</p>
<ul>
<li>《平面设计中的网格系统》,购入这本书是抱着帮助我 Sketch 设计 UI 时对布局做到了然于胸的目的,这本书质量挺高,奈何着重的是出版物排版,和我抱着相同目的的读者不建议作为入门。</li>
<li>《配色设计原理》,不算一部大块头的书,实践性也比较强。主观上感觉不如李涛老师的 PS 高手之路讲解的浅显。
不排除隔行如隔山我的甄别能力跟不上书籍的深度,请谨慎参考。</li>
</ul>
<p>其次是两本建议避免踩坑的书籍。</p>
<ul>
<li>《iOS开发指南:从Hello World到App Store上架》这本书的有效内容集中在图示的具体操作内容。初学 iOS 时购入,即使在当时看来内容也过于浅显并且不够系统。</li>
<li>《编写高质量代码:改善 Objective-C 程序的61个建议》,题为 Objective-C 却有一个专门的章节介绍 Swift,并且专业词汇翻译不准确。将「Block 引用实例属性时不要出现循环引用」翻译为「用块引用其所属对象时不要出现保留环」让人摸不着头脑。</li>
</ul>
<p>剩下的书单没有明确的排序原因。</p>
<ul>
<li>《C Primer Plus》,作为我的编程启蒙书籍现在仍然会时常翻阅,质量很高。</li>
<li>《C陷阱与缺陷》和《C专家编程》,公认的C语言进阶三本推荐读物之二,另一本是《C和指针》。两本书内容上有小部分重叠,对于有C语言编程实践的开发者是不可多得的进阶读物。</li>
<li>《反欺骗的艺术》,出于对信息安全感兴趣购入,同系列的还有《反黑客的艺术》和《反入侵的艺术》。主要讲解社会工程学(出版年份较早,译为社交工程),启迪性不错,作为兴趣读物。</li>
<li>《汇编语言(王爽)》,和《反欺骗的艺术》同为清华出版社出品。出于学习软件逆向购入,豆瓣书评超9分,引入了「知识屏蔽」的独创思想,让整个学习过程非常的顺畅又收获满满,强烈推荐每一个从事编程工作的阅读。</li>
<li>《Pro Git》,考虑到本身电子版免费并且是手册类,所以淘宝购入的影印版。阅读体验一般,适合作为手册查阅。</li>
<li>《Java技术手册》,又是一本手册类的读物,出于学习 Android 开发购入。内容涉及了 Java8 的 lambda 表达式,但同时也包括了 GUI 开发和 JS 交互等不常用的部分,适合作为入门读物。</li>
<li>《Android 编程权威指南》,出于学习 Android 开发购入,内容翔实条理清晰,主观的评价要高于《第一行代码》,顺便一提 Big Nerd Ranch 的其它作品质量也都很高。强烈推荐。</li>
<li>《Python数据分析与挖掘实战》和《Flask Web 开发》,出于学习 Python 购入,两者印象都不深。更建议以《笨办法学Python》或《Python核心编程》作为入门,如果明确的想学习Python数据分析或Python Web开发值得购入。</li>
<li>《编程珠玑》与《编程珠玑(续)》,出于学习算法购入。通过讲故事、提问题的方式展开,习题偏难,不时会翻阅。</li>
<li>《编程之法:面试和算法心得》,某次面试后购入,编程界的《五年高考三年模拟》,书是好书,看不进也是真的看不进。</li>
<li>《数据结构与算法:C语言描述》,如果说上面是《五年高考三年模拟》那么这一本就是考试大纲,想学好算法,读这本就够了。</li>
<li>《七周七并发模型》,讲解了其中语言不同的并发模型,包括锁模型、actor模型、coroutine等,一技傍身不多余。</li>
<li>《重构》和《代码整洁之道》,两本神作。前者教你写出好的代码来完成工作,后者教你写出好的代码来艺术创作。买就完了。</li>
</ul>
<p>老本行iOS的书单。</p>
<ul>
<li>《Objective-C程序设计》,个人愚见:iOS开发的难点始终不在于Objective-C,而在于Cocoa框架、XCode编辑器和iOS系统给你的限制和能力。随着Swift的日益成熟已经没有专门买一本书来学的必要了。吧(心虚)。</li>
<li>《Objective-C编程之道:iOS设计模式解析》,23个设计模式和Java的面向对象实现有很强的关联性,所以不建议通过这本书学习设计模式。比如适配器模式就没感觉哪里适合用,了解生产者消费者模式等倒是有助于写出更加结构化的代码。</li>
<li>《Objective-C高级编程:iOS与OSX多线程和内存管理》,如果iOS书单只保留一本,我会选择这一本。强无敌。</li>
<li>《高性能iOS应用开发》,不错的进阶读物,比如以前没意识到耗电量和流量对用户体验的影响,建议结合WWDC的相关Session阅读。</li>
<li>《iOS应用逆向工程》,这个领域开辟鸿蒙的读物,读完给人跃跃欲试的兴奋感。不过内容对于现在的系统版本可能有些过时。</li>
<li>《精通iOS开发》,广度和深度都不错,尽管我已经忘记了这本书有哪些独有而以上书单不具有的知识点。</li>
</ul>
<p>一些不便归类,拓展视野的书。</p>
<ul>
<li>《MySQL必知必会》,内容精要但足够完备。非常值得入手。</li>
<li>《代码大全(Code Complete)》,极其详尽的讲解了如何设计程序、编写程序、测试程序。书名翻译不准确(译者孟岩说的),其实是从编码到完成的意思,介绍了表驱动法等很有实践意义的内容,示例代码采用C#。</li>
<li>《集体智慧编程》,示例代码采用Python,内容很棒。浅显易懂的解释了如何通过编程实现推荐算法、聚类算法等,强烈推荐。</li>
<li>《编译器设计》和《两周自制脚本语言》,学习编译原理很好的教材,实践性强,前者比龙书对新手要友好地多。</li>
<li>《编码:隐匿在计算机软硬件背后的语言》,非常非常好的一本书,软硬件都有涉及,正在努力啃画满电路图的后半本。</li>
<li>《有趣的二进制》,一位日本作家的逆向工程书籍,内容精要,语言幽默。适合作为入门的兴趣读物。</li>
<li>《图解HTTP》,适合新手进阶的一本书。内容精要,深度一般,后续可以读RFC和《TCP/IP详解》深入学习。</li>
</ul>
<p>买了还没到的书。</p>
<ul>
<li>《软件调试 卷1硬件基础》</li>
<li>《格蠹汇编 软件调试案例集锦》</li>
</ul>
<p>到了还没读的书。</p>
<ul>
<li>《逆向工程核心原理》</li>
<li>《密码学原理与实践》</li>
<li>《Python黑帽子》</li>
<li>《Android安全攻防实践》</li>
<li>《Android软件安全与逆向分析》</li>
<li>《九阴真经 iOS黑客攻防秘籍》(这名字真是够了……)</li>
</ul>
<p>读了还不错的书。</p>
<ul>
<li>《人月神话》</li>
<li>《黑客与画家》</li>
<li>《大教堂与集市》</li>
</ul>ChinsyoCTF入门——从编写简单KeygenMe开始2019-08-17T12:00:00+00:002019-08-17T12:00:00+00:00https://chinsyo.com/2019/08/17/get-started-ctf-deadsimple-keygenme<p>在前一篇文章《CTF入门——从攻破简单CrackMe开始》中介绍了如何通过填充nop编辑可执行文件的二进制内容绕过校验逻辑,本文会进一步介绍如何通过反汇编代码倒推出注册码生成逻辑,编写一个注册机程序。</p>
<p>注册码生成程序也称为 KeygenMe ,通常 CTF 比赛中相关题目爆破得分最低,获得一组注册码次之,注册机得分最高。</p>
<p>回顾查看符号表寻找切入点的步骤,细心的读者想必会有疑问,-[AppDelegate check:] 和 -[AppDelegate checkCode:forName:] 看起来都有可能是校验注册码,为什么我选择了从前者入手呢?</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>nm <span class="nt">-arch</span> i386 DeadSimple
00001d13 t -[AppDelegate check:]
00001daa t -[AppDelegate checkCode:forName:]
00003020 S .objc_class_name_AppDelegate
U .objc_class_name_NSObject
U _NSApplicationMain
U _NSBeep
U _NSRunAlertPanelRelativeToWindow
...
</pre></td></tr></tbody></table></code></pre></div></div>
<p>逆向工程是一种产品设计技术再现过程,本例中用户和产品的交互是通过图形界面进行的。Objective-C 语言中冒号后跟着方法的参数,点击按钮这个交互在 macOS 平台通常通过 Action-Target 模式添加且参数只有一个触发交互的图形控件,由此猜测前者是按钮点击事件。</p>
<p>因此通过 -[AppDelegate checkCode:forName:] 入手同样有效,但是需要对反汇编的逻辑进行倒推,难度较之前会有提升,同时也是本篇的练习目标。</p>
<p>首先还原 check: 和 checkCode:forName: 两者的关系,我们不妨假设前者调用后者并采用后者的返回值决定正确提示或是蜂鸣报错。</p>
<p>对两个函数的开始和结束分别设置断点,结束断点的地址可以由反汇编获得。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>lldb DeadSimple
<span class="o">(</span>lldb<span class="o">)</span> target create <span class="s2">"DeadSimple"</span>
Current executable <span class="nb">set </span>to <span class="s1">'DeadSimple'</span> <span class="o">(</span>i386<span class="o">)</span><span class="nb">.</span>
<span class="o">(</span>lldb<span class="o">)</span> di <span class="nt">-n</span> <span class="s2">"-[AppDelegate check:]"</span>
DeadSimple<span class="sb">`</span>-[AppDelegate check:]:
DeadSimple[0x1d13] <+0>: pushl %ebp
...
DeadSimple[0x1da4] <+145>: leave
DeadSimple[0x1da5] <+146>: jmp 0x4045 <span class="p">;</span> symbol stub <span class="k">for</span>: NSBeep
<span class="o">(</span>lldb<span class="o">)</span> di <span class="nt">-n</span> <span class="s2">"-[AppDelegate checkCode:forName:]"</span>
DeadSimple<span class="sb">`</span>-[AppDelegate checkCode:forName:]:
DeadSimple[0x1daa] <+0>: pushl %ebp
...
DeadSimple[0x1ea4] <+250>: leave
DeadSimple[0x1ea5] <+251>: retl
</pre></td></tr></tbody></table></code></pre></div></div>
<p>得出check:的起始地址为0x1d13,返回地址为0x1da5。checkCode:forName的起始地址为0x1daa,返回地址为0x1ea5。对起始地址和返回地址分别加断点。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="o">(</span>lldb<span class="o">)</span> br s <span class="nt">-n</span> <span class="s2">"-[AppDelegate check:]"</span>
Breakpoint 1: where <span class="o">=</span> DeadSimple<span class="sb">`</span>-[AppDelegate check:], address <span class="o">=</span> 0x00001d13
<span class="o">(</span>lldb<span class="o">)</span> br s <span class="nt">-a</span> 0x1da5
Breakpoint 2: address <span class="o">=</span> 0x00001da5
<span class="o">(</span>lldb<span class="o">)</span> br s <span class="nt">-n</span> <span class="s2">"-[AppDelegate checkCode:forName:]"</span>
Breakpoint 3: where <span class="o">=</span> DeadSimple<span class="sb">`</span>-[AppDelegate checkCode:forName:], address <span class="o">=</span> 0x00001daa
<span class="o">(</span>lldb<span class="o">)</span> br s <span class="nt">-a</span> 0x1ea5
Breakpoint 4: address <span class="o">=</span> 0x00001ea5
</pre></td></tr></tbody></table></code></pre></div></div>
<p>接下来运行程序查看断点触发的顺序,运行之前我们猜想的执行顺序如下图。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-keygenme/01.png" alt="" /></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
</pre></td><td class="rouge-code"><pre>Process 13850 stopped
<span class="k">*</span> thread <span class="c">#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1</span>
frame <span class="c">#0: 0x00001d13 DeadSimple`-[AppDelegate check:]</span>
DeadSimple<span class="sb">`</span>-[AppDelegate check:]:
-> 0x1d13 <+0>: pushl %ebp
0x1d14 <+1>: movl %esp, %ebp
0x1d16 <+3>: pushl %esi
0x1d17 <+4>: pushl %ebx
Target 0: <span class="o">(</span>DeadSimple<span class="o">)</span> stopped.
<span class="o">(</span>lldb<span class="o">)</span> c
Process 13850 resuming
Process 13850 stopped
<span class="k">*</span> thread <span class="c">#1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1</span>
frame <span class="c">#0: 0x00001daa DeadSimple`-[AppDelegate checkCode:forName:]</span>
DeadSimple<span class="sb">`</span>-[AppDelegate checkCode:forName:]:
-> 0x1daa <+0>: pushl %ebp
0x1dab <+1>: movl %esp, %ebp
0x1dad <+3>: subl <span class="nv">$0x28</span>, %esp
0x1db0 <+6>: movl 0x3008, %eax
Target 0: <span class="o">(</span>DeadSimple<span class="o">)</span> stopped.
<span class="o">(</span>lldb<span class="o">)</span> c
Process 13850 resuming
Process 13850 stopped
<span class="k">*</span> thread <span class="c">#1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1</span>
frame <span class="c">#0: 0x00001ea5 DeadSimple`-[AppDelegate checkCode:forName:] + 251</span>
DeadSimple<span class="sb">`</span>-[AppDelegate checkCode:forName:]:
-> 0x1ea5 <+251>: retl
0x1ea6: addb %al, <span class="o">(</span>%eax<span class="o">)</span>
0x1ea8: addb %al, <span class="o">(</span>%eax<span class="o">)</span>
0x1eaa: addb %al, <span class="o">(</span>%eax<span class="o">)</span>
Target 0: <span class="o">(</span>DeadSimple<span class="o">)</span> stopped.
<span class="o">(</span>lldb<span class="o">)</span> c
Process 13850 resuming
Process 13850 stopped
<span class="k">*</span> thread <span class="c">#1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1</span>
frame <span class="c">#0: 0x00001da5 DeadSimple`-[AppDelegate check:] + 146</span>
DeadSimple<span class="sb">`</span>-[AppDelegate check:]:
-> 0x1da5 <+146>: jmp 0x4045 <span class="p">;</span> symbol stub <span class="k">for</span>: NSBeep
DeadSimple<span class="sb">`</span>-[AppDelegate checkCode:forName:]:
0x1daa <+0>: pushl %ebp
0x1dab <+1>: movl %esp, %ebp
0x1dad <+3>: subl <span class="nv">$0x28</span>, %esp
Target 0: <span class="o">(</span>DeadSimple<span class="o">)</span> stopped.
</pre></td></tr></tbody></table></code></pre></div></div>
<p>断点触发顺序和我们猜想的一致,分析校验逻辑需要熟悉汇编语言,这时祭出我们的神器 Hopper,站在巨人的肩膀上继续。</p>
<p>通常进行静态分析之前需要对应用脱壳,本例没有加壳所以可以直接分析。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-keygenme/02.jpeg" alt="" /></p>
<p>拖入 Hopper 依次点击1、2后查看反汇编代码,和在 lldb 中执行 disassemble 结果基本一致,这时点击3可以查看生成的伪代码。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-keygenme/03.jpeg" alt="" /></p>
<p>生成的伪代码已经非常接近真实代码的流程,根据上下文可以推断出返回类型是 BOOL,arg2 和 arg3 应该是 NSString 类型。</p>
<p>cvtsi2sd是将DWORD(4字节)整型数转换为浮点型数,cvttsd2si则反之,sqrtsd是开方运算。前往 https://www.felixcloutier.com/x86/ 查看指令详情。</p>
<p>对照伪代码写出Objective-C的代码,得出结论注册码有以下几个要求:</p>
<ul>
<li>注册码应该为使用连字符连接的两个数字</li>
<li>连字符的后半部分数字应该为前半部分数字的平方</li>
<li>连字符的前半部分数字应该为 name 字符串 ascii 编码的数值之和</li>
</ul>
<div class="language-objc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="k">-</span> <span class="p">(</span><span class="n">BOOL</span><span class="p">)</span><span class="nf">checkCode</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">code</span> <span class="nf">forName</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">name</span> <span class="p">{</span>
<span class="n">NSArray</span> <span class="o">*</span><span class="n">comps</span> <span class="o">=</span> <span class="p">[</span><span class="n">code</span> <span class="nf">componentsSeparatedByString</span><span class="p">:</span><span class="s">@"-"</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">comps</span><span class="p">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">headValue</span> <span class="o">=</span> <span class="p">[[</span><span class="n">comps</span> <span class="nf">objectAtIndex</span><span class="p">:</span><span class="mi">0</span><span class="p">]</span> <span class="nf">intValue</span><span class="p">];</span>
<span class="n">NSString</span> <span class="o">*</span><span class="n">tailString</span> <span class="o">=</span> <span class="p">[</span><span class="n">comps</span> <span class="nf">objectAtIndex</span><span class="p">:</span><span class="mi">1</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">tailValue</span> <span class="o">=</span> <span class="p">[</span><span class="n">tailString</span> <span class="nf">intValue</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">sqrtf</span><span class="p">((</span><span class="kt">float</span><span class="p">)</span><span class="n">tailValue</span><span class="p">)</span> <span class="o">==</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">headValue</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">i</span> <span class="o"><</span> <span class="n">tailString</span><span class="p">.</span><span class="n">length</span><span class="p">)</span> <span class="p">{</span>
<span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">headValue</span> <span class="o">-=</span> <span class="p">([</span><span class="n">name</span> <span class="nf">characterAtIndex</span><span class="p">:</span><span class="n">i</span><span class="p">]</span> <span class="o">&</span> <span class="mh">0xffff</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>对照着校验逻辑,不难编写出注册码生成程序。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="c1">#! /usr/bin/env python3
</span><span class="kn">from</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="n">argv</span>
<span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="nb">reduce</span>
<span class="k">def</span> <span class="nf">strsum</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
<span class="n">nums</span> <span class="o">=</span> <span class="p">[</span><span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">name</span><span class="p">)]</span>
<span class="k">return</span> <span class="nb">reduce</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">:</span> <span class="n">x</span><span class="o">+</span><span class="n">y</span><span class="p">,</span> <span class="n">nums</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
<span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">argv</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span><span class="p">,</span> <span class="s">"用户名不能为空"</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">v1</span> <span class="o">=</span> <span class="n">strsum</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"{}: {}-{}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">v1</span><span class="p">,</span> <span class="n">v1</span><span class="o">*</span><span class="n">v1</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>运行我们的注册码生成程序,复制控制台输出。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>python3 deadsimple-keygen.py <span class="s2">"chenxiao"</span>
chenxiao: 847-717409
</pre></td></tr></tbody></table></code></pre></div></div>
<p>将控制台的输出填入输入框,点击 Check 校验。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-keygenme/04.png" alt="" /></p>
<p>显示正确,撒花!🎉</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-keygenme/05.jpeg" alt="" /></p>
<p>以上分析的流程同样适用于现实中的软件激活,不过切记遵守法律法规。最后附一张安全专家tk教主的某乎答案自我惕励。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-keygenme/06.jpeg" alt="" /></p>Chinsyo在前一篇文章《CTF入门——从攻破简单CrackMe开始》中介绍了如何通过填充nop编辑可执行文件的二进制内容绕过校验逻辑,本文会进一步介绍如何通过反汇编代码倒推出注册码生成逻辑,编写一个注册机程序。CTF入门——从攻破简单CrackMe开始2019-08-16T12:00:00+00:002019-08-16T12:00:00+00:00https://chinsyo.com/2019/08/16/get-started-ctf-deadsimple-crackme<p>也许你还不知道什么是CTF和RE(Reverse Engineer 逆向工程),但你可能已经被韩商言和李现刷屏了几个轮回,最近人气和口碑双丰收的电视剧《亲爱的 热爱的》对CTF这个安全领域的概念做了全民科普。</p>
<p>某天我家那位不知防火墙是何物的人民教师和我闲聊时问起CTF,作为网安公司的研发工程师既诧异又欣慰,诧异的是CTF早于信息安全本身走近大众,欣慰的是在格子衫和秃头怪的调侃声中,电视剧居然率先为这个行业正名。</p>
<p>CTF(Capture The Flag 夺旗赛)是信息安全技术竞赛的一种形式,而逆向工程是信息安全技术的一个范畴,国内知名安全论坛看雪主办的KCTF就是由CrackMe攻防大赛发展而来。</p>
<p>CrackMe是一类供人尝试破解的程序,攻击者和发布者就软件保护和破解展开较量,通常以输入通过软件校验注册码为目标。</p>
<p>由于本职工作iOS研发工程师的缘故,本次示例以macOS系统为例。首先我们需要找到攻破的目标即CrackMe程序,访问 https://reverse.put.as/crackmes/ 找到页面最下方的DeadSimple进行下载,从未知来源下载软件要记得校验摘要和查杀病毒,下面是DeadSimple的摘要信息和查杀结果。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-crackme/01.jpeg" alt="" /></p>
<p>(吐槽一下腾讯,原本打算选择腾讯哈勃的扫描结果,可我一个macOS程序居然给我扫出C盘路径和写入注册表……拜拜了您呐。)</p>
<p>先双击运行程序看看效果,不出意外会弹出两个弹窗,分别如图。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-crackme/02.png" alt="" /></p>
<p>首先提示不是通过AppStore下载,并且没有经过开发者证书签名。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-crackme/03.png" alt="" /></p>
<p>接着提示尚未针对您的Mac优化,这是因为CrackMe程序不包含当前x86_64的架构。</p>
<p>前往 系统偏好设置->安全与隐私 允许安装,如下图,点击「仍要打开」。如果你的页面没有「允许从以下位置下载的应用」或运行时提醒「文件已损坏」,请参考 https://baijiahao.baidu.com/s?id=1594877309704492558 进行设置。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-crackme/04.jpeg" alt="" /></p>
<p>启动后界面包含Name和Code两个输入框,和常见的激活界面相似。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-crackme/05.png" alt="" /></p>
<p>这时随便输入一些信息,如123,123。校验失败会听到错误提示音。</p>
<p>让我们开动起来尝试攻破第一个CrackMe,首先切换到可执行文件的工作路径,以桌面为例,路径为 ~/Desktop/DeadSimple.app/Contents/MacOS ,查看符号表寻找切入点。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>file DeadSimple
DeadSimple: Mach-O universal binary with 2 architectures: <span class="o">[</span>i386:Mach-O executable i386] <span class="o">[</span>ppc:Mach-O executable ppc]
DeadSimple <span class="o">(</span><span class="k">for </span>architecture i386<span class="o">)</span>: Mach-O executable i386
DeadSimple <span class="o">(</span><span class="k">for </span>architecture ppc<span class="o">)</span>: Mach-O executable ppc
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-m</span>
x86_64
</pre></td></tr></tbody></table></code></pre></div></div>
<p>当前系统架构x86_64,而可执行文件架构为i386和ppc,可见这个CrackMe年久失修,ppc是苹果在采用intel处理器之前使用的IBM PowerPC架构,所以软件是以i386兼容模式运行的。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>nm <span class="nt">-arch</span> i386 DeadSimple
00001d13 t -[AppDelegate check:]
00001daa t -[AppDelegate checkCode:forName:]
00003020 S .objc_class_name_AppDelegate
U .objc_class_name_NSObject
U _NSApplicationMain
U _NSBeep
U _NSRunAlertPanelRelativeToWindow
0000200c D _NXArgc
00002008 D _NXArgv
U ___CFConstantStringClassReference
U ___keymgr_dwarf2_register_sections
00002000 D ___progname
U __cthread_init_routine
00001d04 t __dyld_func_lookup
00001000 A __mh_execute_header
00001c16 t __start
U _atexit
00002058 S _catch_exception_raise
0000205c S _catch_exception_raise_state
00002060 S _catch_exception_raise_state_identity
00002064 S _clock_alarm_reply
00002068 S _do_mach_notify_dead_name
0000206c S _do_mach_notify_no_senders
00002070 S _do_mach_notify_port_deleted
00002074 S _do_mach_notify_send_once
00002078 S _do_seqnos_mach_notify_dead_name
0000207c S _do_seqnos_mach_notify_no_senders
00002080 S _do_seqnos_mach_notify_port_deleted
00002084 S _do_seqnos_mach_notify_send_once
00002004 D _environ
U _errno
U _exit
U _mach_init_routine
00001d0a t _main
U _objc_msgSend
00002088 S _receive_samples
00001cf8 t dyld_stub_binding_helper
00001bec T start
</pre></td></tr></tbody></table></code></pre></div></div>
<p>macOS 平台编程语言 Objective-C 使用中括号表示方法,中括号前+表示类方法,-表示实例方法。看样子 -[AppDelegate check:] 就是我们要找的入手点,lldb挂载调试。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>lldb DeadSimple
<span class="o">(</span>lldb<span class="o">)</span> target create <span class="s2">"DeadSimple"</span>
Current executable <span class="nb">set </span>to <span class="s1">'DeadSimple'</span> <span class="o">(</span>i386<span class="o">)</span><span class="nb">.</span>
<span class="o">(</span>lldb<span class="o">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>根据前一步推理,查看对应的反汇编代码。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
</pre></td><td class="rouge-code"><pre><span class="o">(</span>lldb<span class="o">)</span> di <span class="nt">-n</span> <span class="s2">"-[AppDelegate check:]"</span>
DeadSimple<span class="sb">`</span>-[AppDelegate check:]:
DeadSimple[0x1d13] <+0>: pushl %ebp
DeadSimple[0x1d14] <+1>: movl %esp, %ebp
DeadSimple[0x1d16] <+3>: pushl %esi
DeadSimple[0x1d17] <+4>: pushl %ebx
DeadSimple[0x1d18] <+5>: subl <span class="nv">$0x20</span>, %esp
DeadSimple[0x1d1b] <+8>: movl 0x8<span class="o">(</span>%ebp<span class="o">)</span>, %esi
DeadSimple[0x1d1e] <+11>: movl 0x3000, %eax
DeadSimple[0x1d23] <+16>: movl 0x8<span class="o">(</span>%esi<span class="o">)</span>, %edx
DeadSimple[0x1d26] <+19>: movl %eax, 0x4<span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d2a] <+23>: movl %edx, <span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d2d] <+26>: calll 0x405e <span class="p">;</span> symbol stub <span class="k">for</span>: objc_msgSend
DeadSimple[0x1d32] <+31>: movl 0xc<span class="o">(</span>%esi<span class="o">)</span>, %edx
DeadSimple[0x1d35] <+34>: movl %eax, %ebx
DeadSimple[0x1d37] <+36>: movl 0x3000, %eax
DeadSimple[0x1d3c] <+41>: movl %edx, <span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d3f] <+44>: movl %eax, 0x4<span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d43] <+48>: calll 0x405e <span class="p">;</span> symbol stub <span class="k">for</span>: objc_msgSend
DeadSimple[0x1d48] <+53>: movl %ebx, 0xc<span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d4c] <+57>: movl %eax, 0x8<span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d50] <+61>: movl 0x3004, %eax
DeadSimple[0x1d55] <+66>: movl %esi, <span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d58] <+69>: movl %eax, 0x4<span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d5c] <+73>: calll 0x405e <span class="p">;</span> symbol stub <span class="k">for</span>: objc_msgSend
DeadSimple[0x1d61] <+78>: testb %al, %al
DeadSimple[0x1d63] <+80>: je 0x1d9f <span class="p">;</span> <+140>
DeadSimple[0x1d65] <+82>: movl 0x4<span class="o">(</span>%esi<span class="o">)</span>, %eax
DeadSimple[0x1d68] <+85>: movl <span class="nv">$0x0</span>, 0x10<span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d70] <+93>: movl <span class="nv">$0x0</span>, 0xc<span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d78] <+101>: movl <span class="nv">$0x2018</span>, 0x8<span class="o">(</span>%esp<span class="o">)</span> <span class="p">;</span> imm <span class="o">=</span> 0x2018
DeadSimple[0x1d80] <+109>: movl %eax, 0x14<span class="o">(</span>%esp<span class="o">)</span>
DeadSimple[0x1d84] <+113>: movl <span class="nv">$0x2028</span>, 0x4<span class="o">(</span>%esp<span class="o">)</span> <span class="p">;</span> imm <span class="o">=</span> 0x2028
DeadSimple[0x1d8c] <+121>: movl <span class="nv">$0x2038</span>, <span class="o">(</span>%esp<span class="o">)</span> <span class="p">;</span> imm <span class="o">=</span> 0x2038
DeadSimple[0x1d93] <+128>: calll 0x404a <span class="p">;</span> symbol stub <span class="k">for</span>: NSRunAlertPanelRelativeToWindow
DeadSimple[0x1d98] <+133>: addl <span class="nv">$0x20</span>, %esp
DeadSimple[0x1d9b] <+136>: popl %ebx
DeadSimple[0x1d9c] <+137>: popl %esi
DeadSimple[0x1d9d] <+138>: leave
DeadSimple[0x1d9e] <+139>: retl
DeadSimple[0x1d9f] <+140>: addl <span class="nv">$0x20</span>, %esp
DeadSimple[0x1da2] <+143>: popl %ebx
DeadSimple[0x1da3] <+144>: popl %esi
DeadSimple[0x1da4] <+145>: leave
DeadSimple[0x1da5] <+146>: jmp 0x4045 <span class="p">;</span> symbol stub <span class="k">for</span>: NSBeep
<span class="o">(</span>lldb<span class="o">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>输出的信息很长,鉴于可能不是每位读者都十分熟悉汇编语言,这里做简要的补充。retl表示返回,movl表示赋值,je(jump equal)表示当前一条指令的对比结果相同时跳转到指定地址继续执行,否则按顺序向下继续执行。</p>
<p>所以,简要的了解这段汇编的执行顺序后,发现这段汇编程序只有一个分支判断。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>DeadSimple[0x1d63] <+80>: je 0x1d9f
</pre></td></tr></tbody></table></code></pre></div></div>
<p>再观察分支的执行情况,不难看出0x1d9f地址执行的是恢复栈底并蜂鸣报错。</p>
<p>所以,绕过这个检查的方式之一就是跳过0x1d63的判断逻辑。在汇编语言中,nop指令表示no operation即不做操作,opcode为0x90。je 0x1d63的下一条指令地址为0x1d65,0x1d65-0x1d63=2,所以我们接下来要做的就是对0x1d63开始的2个字节填充0x90。</p>
<p>使用vi以二进制方式打开可执行文件。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>vi <span class="nt">-b</span> DeadSimple
</pre></td></tr></tbody></table></code></pre></div></div>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-crackme/06.jpeg" alt="" /></p>
<p>放眼望去满是^和@组成的符号,这是因为二进制文件不是有效的ascii可视字符,在vi输入:%!xxd 进入hex编辑模式,这下顺眼多了。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-crackme/07.jpeg" alt="" /></p>
<p>在vi中移动光标到0x1d63对应的地址(搜索1d60,然后向后数3字节即6个hex字符),将 74 3a 修改为 90 90。输入:%xxd -r 退出hex模式并保存文件。</p>
<p>这时再次运行软件,随便输入内容,可以看到成功提示弹窗。</p>
<p><img src="https://cdn.chinsyo.com/img/dead-simple-crackme/08.png" alt="" /></p>
<p>友情提示,修改完毕二进制后可以重新查看反汇编检验修改的结果再运行程序,由于篇幅不再展开。下一篇将围绕如何倒推校验逻辑,编写一个注册码生成程序。</p>Chinsyo也许你还不知道什么是CTF和RE(Reverse Engineer 逆向工程),但你可能已经被韩商言和李现刷屏了几个轮回,最近人气和口碑双丰收的电视剧《亲爱的 热爱的》对CTF这个安全领域的概念做了全民科普。米家智能家居选购指南 & 体验报告2019-08-11T12:00:00+00:002019-08-11T12:00:00+00:00https://chinsyo.com/2019/08/11/mijia-smart-home-tricks-and-tips<p>家是家庭成员的连结点,承载了生产活动之外的几乎所有时间。按照马斯洛需求金字塔,家居的需求也可以分为安全、舒适、美观、享受几个层次,作为一名研发人员,一直期望家能更有科技感。</p>
<p>智能家居作为物联网的一个应用领域不断有产品推陈出新,要在名目繁多的产品中找到自己需要的并不容易。我在2018年中旬思考、规划和选购了自己的智能家居方案,目前使用一年有余,本篇会从选购指南和体验报告两方面展开。</p>
<p>物联网和互联网不同之处在于接入网络的不再局限于智能设备终端,而是包括了机械、电器、传感器等。和智能设备终端不同,智能家居对安全性有着更高的要求,所以选择一个接入设备更多、涵盖范围更广的品牌有利于后期的升级,联动性更强也意味着可玩性更高。</p>
<p>智能家居的整体方案主要可以归纳为四个:</p>
<ul>
<li>苹果公司的 HomeKit 整体方案,使用「家庭」应用、Siri或者Homepod来控制设备。</li>
<li>阿里公司的整体方案,整合了博通(BroadLink)、萤石等品牌,优势在于整合的白色家电品牌多,使用天猫精灵来控制设备。</li>
<li>小米的米家智能家居,产品多为小米投资的产业链下游企业,产品覆盖全面,使用小爱同学或者「米家」应用来控制设备。</li>
<li>基于Home Assistant和树莓派等定制方案,小度音响的语音识别模块DuerOS代码已开源。</li>
</ul>
<p>横向对比之后进入欲购清单的只剩下小米和阿里,最后由于外观设计,权衡之下决定选择米家智能家居。</p>
<p>从功能上划分为网关、开关、传感器、小家电、大家电等;从连接方式上划分为Wi-Fi,蓝牙,ZigBee,网线等;从品牌上主要有米家和Aqara(绿米)两个选择。</p>
<p>晕了吗?没晕的话再读一次,选购时我被这些一度搞晕。下面和大家分享我最终的采购清单:</p>
<ul>
<li>Aqara空调伴侣(升级版) x 2</li>
<li>Aqara智能墙壁插座 x 1</li>
<li>Aqara智能墙壁开关 单火版 单键版 x 2</li>
<li>小米米家智能插座 Wi-Fi 版 白色 x 1</li>
<li>米家温湿度传感器 白色 x 1</li>
<li>Aqara温湿度传感器 x 1</li>
<li>米家人体传感器 白色 x 1</li>
<li>Aqara人体传感器 x 1</li>
<li>米家门窗传感器 白色 x 1</li>
<li>米家无线开关 白色 x 1</li>
<li>小米米家空气净化器 2S x 1</li>
<li>飞利浦智睿球泡灯 白色 x 1</li>
<li>小米米家智能摄像机云台版 白色 x 1</li>
<li>小米AI音箱 x 1</li>
<li>小米路由器4A 白色 x 1</li>
</ul>
<p>其中门窗感应器、摄像机和智能网关(空调伴侣)的报警功能是为了保证安全;空调伴侣配合温湿度传感器、空气净化器提高居住的舒适度;美观和享受不是我们无产阶级需要在意的事情。</p>
<p>家居中有四个维度是我认为舒适度的决定性因素:噪音、水质、温湿度、空气质量。双层中空玻璃基本满足了我对噪音的要求,水质与其购入设备定期更换滤芯,我选择了更加一劳永逸的订桶装水。</p>
<p>说完了选购清单再来说一下选购指南:</p>
<ul>
<li>Aqara(绿米)产品定位略高于米家,通常会增加非核心功能,比如温湿度传感器增加了气压,空调伴侣增加了电台功能。米家足够日常使用。</li>
<li>一些产品同时有 Wi-Fi 版、蓝牙版、ZigBee 版等时一律选择ZigBee版,能耗控制和接入数量好于前两者。</li>
<li>墙壁插座额定功率400W,卫生间装有浴霸或风暖的可能无法使用(和萤石的朋友确认过也是这样)。</li>
<li>家里养猫的朋友,人体传感器和顶灯联动可能比较鬼畜。以至于我的人体传感器在吃灰。</li>
<li>iOS端的地理围栏功能触发精度不够高,当然不排除是我房子太小的缘故😅。</li>
</ul>
<p>初期考虑购买但最终没有购买的产品有智能窗帘、空气加湿器、扫地机器人和净水系统。智能窗帘是考虑到每天早上给我猝不及防的一束阳光并不让人期待,加上窗帘杆两端预留的电机空间不够;由于在上海工作时每天和除湿斗智斗勇的经历,空气加湿器和本次选购绝缘;扫地机器人考虑到微博上著名的「猫+扫地机器人=滑翔」加上家具都是落地款打扫区域有限就放弃了;净水还是因为懒得换滤芯加上习惯和纯净水。</p>
<p>需要声明的是,<em>在此之前我从未买过小米的任何产品,包括但不限于手机、移动电源和小米手环等主流产品。</em></p>
<p>一年多体验下来这个方案整体让人满意,也有要点名表扬和不吐不快的地方:</p>
<ul>
<li>小米路由器属实🌶️🐔,在千兆遍地的 2018 年坚持百兆 WAN 口,其他品牌无论比它贵的还是比它便宜的,统统没有比它差的。用朋友的话说:小米路由器是米吹都没脸吹的产品。</li>
<li>小爱同学放在客厅,每天早晨的闹钟你需要用盖过它音量的声音大声呵斥「小爱同学,闭嘴」来关闭,喊完之后毫无困意,叫醒体验满分。</li>
<li>设置场景后,睡前只需要说一声「小爱同学,晚安」就可以关闭客厅灯、阅读灯、客厅空调,并将空气净化器调整成睡眠模式,智能网关开启报警模式。非常方便。</li>
<li>门窗感应器+摄像机的组合,在外出时给你很大的信心,远程查看监控还能隔空喊话,挺有意思的体验。</li>
<li>飞利浦变色灯泡物美价廉,可惜我没有在阅读时开阅读灯的习惯,持续吃灰中。</li>
<li>小爱同学的唤醒有时候会抽风,两个人正在对话被错误的识别,冒出莫名其妙的应答。</li>
</ul>
<p>感兴趣的朋友不妨着手选购一个自己的方案,解放琐碎的事情,把家变得智能。</p>Chinsyo家是家庭成员的连结点,承载了生产活动之外的几乎所有时间。按照马斯洛需求金字塔,家居的需求也可以分为安全、舒适、美观、享受几个层次,作为一名研发人员,一直期望家能更有科技感。FRP 内网穿透——随时随地SSH连接家中的树莓派2019-08-10T12:00:00+00:002019-08-10T12:00:00+00:00https://chinsyo.com/2019/08/10/ssh-connect-raspberry-pi-anywhere<p>树莓派(Raspberry Pi)是为计算机编程教育设计的廉价电脑,只有信用卡大小。</p>
<p>久闻树莓派盛名,第一次动购买的念头是看了 Kitten Yang 基于 Home Assistant 搭建智能家居的博文后,但装修时最终采用了小米智能家居的整体方案(目前已经使用一年有余,今后会专门写一篇使用体验)。今年上半年做了一些贴近硬件的软件开发,萌生了探索硬件的念头,遂购入树莓派。</p>
<p>我选购的是彼时最新型号树莓派3B+,最新发布的树莓派4有着不俗的性能提升。回顾折腾树莓派的过程,有几点经验分享:</p>
<ol>
<li>网线和HDMI视频线不是必须的,将系统烧录到SD卡后,配置 Wi-Fi 连接功能和SSH功能。然后使用同一局域网的电脑<code class="language-plaintext highlighter-rouge">ping raspberrypi.local</code>获得IP地址,使用ZSH(或手机使用Termius)连接到树莓派。</li>
<li>风扇的噪音比较大,可以选择铝合金外壳来改善散热或通过三极管调节风扇风速减小噪音。</li>
<li>默认的APT源下载速度比较慢,建议换成清华或中科大的源。</li>
<li>APT源中的golang版本比较老,需要通过APT安装golang后再下载新版本源码自行编译。</li>
</ol>
<p>树莓派买来后我主要用在以下几方面:</p>
<ol>
<li>搭建SAMBA文件共享服务,当一个简易的NAS,备份了一些照片和文件。</li>
<li>搭建Aria2+YAAW配合VLC的简易影视系统,用来看了几部电影,印象最深刻的当属《泰坦尼克号》。</li>
<li>将一些格式转换的任务丢给FFmpeg慢慢执行。</li>
<li>定时运行一些简易的定时脚本。</li>
</ol>
<p>当然,就像「盖Kindle 面更香」一样,树莓派最主要的用途还是吃灰。</p>
<p>贸易战的消息此起彼伏,对美国,大家的态度多是忌惮和愤恨兼而有之。最近更是曝出Github受到美国出口管理条例的管制,无故停用了几个伊朗开发者的账号。</p>
<p>就像华为有HarmonryOS作为Android备胎那样,我决定部署一个自己的Git服务,不使用GitHub也不依托云主机,树莓派刚好派上用场。</p>
<p>说干就干,摆在眼前的第一个问题是,我的宽带是移动冰淇淋套餐赠送的家庭宽带,没有公网IP。解决方案主要有三个:</p>
<ul>
<li>花生壳等DDNS解析</li>
<li>Ngrok</li>
<li>FRP</li>
</ul>
<p>付费服务花生壳不考虑,Ngrok免费版不支持自定义域名,这次决定挑没玩过的FRP下手。</p>
<p>FRP是Fast Reverse Proxy的缩写,一款支持TCP/UDP快速反向代理的开源软件,可以很方便的内网穿透。和花生壳、Ngrok等不同,FRP客户端和服务端的控制权都在自己手中,这也意味着你需要准备一台有公网IP的VPS运行服务端程序。</p>
<p>好巧不巧,腾讯云99元/年的AMD主机我刚好有一台在吃灰。首先分别查看服务器和树莓派的系统架构。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="c"># Server</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-a</span>
Linux VM-0-2-ubuntu 4.4.0-148-generic <span class="c">#174-Ubuntu SMP Tue May 7 12:20:14 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux</span>
<span class="c"># Raspberry Pi</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-a</span>
Linux raspberrypi 4.19.57-v7+ <span class="c">#1244 SMP Thu Jul 4 18:45:25 BST 2019 armv7l GNU/Linux</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>访问FRP的<a href="https://github.com/fatedier/frp/releases">release页面</a>查看对应架构的最新版下载地址,在服务器和树莓派分别下载并解压。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span><span class="nb">mkdir</span> <span class="nt">-p</span> app/install <span class="o">&&</span> <span class="nb">cd </span>app/install
<span class="nv">$ </span>wget https://github.com/fatedier/frp/releases/download/v0.28.2/frp_0.28.2_linux_arm.tar.gz
<span class="nv">$ </span><span class="nb">tar</span> <span class="nt">-zxvf</span> frp_0.28.2_linux_arm.tar.gz
<span class="nv">$ </span><span class="nb">sudo mv </span>frp_0.28.2_linux_arm /usr/local/frp
</pre></td></tr></tbody></table></code></pre></div></div>
<p>以上步骤服务器和树莓派一致,上面的示例是针对树莓派3B+的armv7l架构,服务器注意替换为amd64的下载链接。</p>
<p>接下来需要修改配置文件,树莓派配置文件为frpc.ini,服务器配置文件为frps.ini,对应C/S架构中的服务端和客户端。服务器采用默认配置,树莓派将表示服务器地址的<code class="language-plaintext highlighter-rouge">x.x.x.x</code>替换为实际地址。</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c"># frps.ini
</span><span class="nn">[common]</span>
<span class="py">bind_port</span> <span class="p">=</span> <span class="s">7000</span>
<span class="c"># frpc.ini
</span><span class="nn">[common]</span>
<span class="py">server_addr</span> <span class="p">=</span> <span class="s">x.x.x.x</span>
<span class="py">server_port</span> <span class="p">=</span> <span class="s">7000</span>
<span class="nn">[ssh]</span>
<span class="py">type</span> <span class="p">=</span> <span class="s">tcp</span>
<span class="py">local_ip</span> <span class="p">=</span> <span class="s">127.0.0.1</span>
<span class="py">local_port</span> <span class="p">=</span> <span class="s">22</span>
<span class="py">remote_port</span> <span class="p">=</span> <span class="s">6000</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>接着切换到上面的工作路径<code class="language-plaintext highlighter-rouge">/usr/local/frp</code>,服务端执行<code class="language-plaintext highlighter-rouge">./frps -c frps.ini</code>,树莓派执行<code class="language-plaintext highlighter-rouge">./frpc -c ./frpc.ini</code>。</p>
<p>电脑打开ZSH,或手机打开Termius,<code class="language-plaintext highlighter-rouge">ssh -oPort=6000 pi@x.x.x.x</code>测试我们的连接。注意这里pi代表在树莓派上的用户名,而x.x.x.x代表服务器的IP地址,正常情况下这个时候你会看到熟悉的Linux登录成功提示。</p>
<p>可我没有,转念一想,应该是防火墙的问题。前往腾讯云的控制台,在安全组中添加以下两条:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>0.0.0.0/0 TCP:6000
0.0.0.0/0 TCP:7000
</pre></td></tr></tbody></table></code></pre></div></div>
<p>再次测试ssh连接,终于见到了熟悉的Linux登录成功提示。</p>
<p>到此为止已经实现了随时随地SSH连接家中的树莓派,通过下面的命令可以后台运行并记录日志。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="c"># Server</span>
<span class="nb">nohup</span> ./frps <span class="nt">-c</span> ./frps.ini <span class="o">></span> frps.log 2>&1 &
<span class="c"># Raspberry Pi</span>
<span class="nb">nohup</span> ./frpc <span class="nt">-c</span> ./frpc.ini <span class="o">></span> frpc.log 2>&1 &
</pre></td></tr></tbody></table></code></pre></div></div>
<p>出于安全考虑,可以在服务器和树莓派的配置文件中增加token字段,服务器和树莓派的token字段需要保持一致。建议生成随机数或伪随机数,我最终采用的是<code class="language-plaintext highlighter-rouge">date | md5 | head -c 8</code>的输出结果。</p>
<p>由于暴躁的我常常出于无法忍受树莓派的噪音而关机,有必要将FRP加入开机启动。</p>
<p>首先创建service:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>vi /etc/systemd/system/frpc.service
<span class="c"># /etc/systemd/system/frpc.service</span>
<span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>frpc
<span class="nv">After</span><span class="o">=</span>network.target
<span class="o">[</span>Service]
<span class="nv">TimeoutStartSec</span><span class="o">=</span>30
<span class="nv">WorkingDirectory</span><span class="o">=</span>/usr/local/frp
<span class="nv">ExecStart</span><span class="o">=</span>/usr/local/frp/frpc <span class="nt">-c</span> /usr/local/frp/frpc.ini
<span class="nv">Restart</span><span class="o">=</span>on-failure
<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>multi-user.target
</pre></td></tr></tbody></table></code></pre></div></div>
<p>服务器和树莓派配置过程相同,服务器只需要将frpc替换为frps即可。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="c"># 刷新服务</span>
<span class="nb">sudo </span>systemctl daemon-reload
<span class="c"># 允许开机启动</span>
<span class="nb">sudo </span>systemctl <span class="nb">enable </span>frpc.service
<span class="c"># 运行服务</span>
<span class="nb">sudo </span>systemctl start frpc.service
<span class="c"># 查看状态</span>
<span class="nb">sudo </span>systemctl status frpc.service
</pre></td></tr></tbody></table></code></pre></div></div>
<p>到这里开机启动也设置完毕了,树莓派远程吃灰的玩法等你开发。:)</p>Chinsyo树莓派(Raspberry Pi)是为计算机编程教育设计的廉价电脑,只有信用卡大小。