[技术杂谈]低成本搭建iOS原生VPN,告别小火箭

贫穷再一次激起了人的创造力 上一次:[技术杂谈]低成本windows远程控制直连方案(无需VPN组网) 前言 这次事情的起因是最近换了新的传输协议xhttp,三台电脑上平时在用的xray和v2ray内核都成功跑通了,本以为可以在我的16e上愉快的使用时,却发现自己的小火箭内核版本过低,传输协议选项中没有xhttp 那么看到这解决办法肯定就是要去更新,但由于我的apple id目前在国区,要更新就得换到当时买火箭的美区。但又因为我在国区有apple music,此时如果换区就会导致我的资料库被删掉,就此触发音乐圈经典老番🤣👉 我可不想自己整理了大半年的音乐库凭空消失,所以换区这条路肯定是不行了。此时肯定有聪明的观众会说可以换一个在美区并且有火箭的apple id,但这又会导致我apple钱包里的卡被全部解绑,换回我自己的国区id时得一张一张重新绑。更重要的是,必须是”有火箭“的美区id,相当于我又得重新花二十多块钱买个火箭,或者还得找人去借一个 所以,求人不如求己,一个黑客最基本的素养就是要想办法花最少的钱办最大的事,就有了这篇博客 (另外顺便解释一下为什么需要小火箭这类网卡层代理,而不使用无线网络连接时设置里的http/socks5代理?原因其实很简单,一是DNS泄漏的问题(可以看[技术杂谈]一篇聊完DNS投毒、泄露),二是iphone上绝大多数应用会无视这个设置导致仍然无法使用) 需要准备 一台闲置linux服务器。用来跑xray/v2ray/singbox/clash等各种代理内核,我用的是家里的一台闲置电脑。如果你没有,也可以随便拿台vps,不过最好不要是国内厂商的,原因你懂的 (选用)一个公网ipv4地址。最好的办法就是随便找台带公网v4地址的vps,我自己正好手头有台闲置的阿里云。这台vps就可以是国内厂商,并且最好是在国内,因为是出门在外做转发会用到,下文会介绍原因以及为什么不能是ipv6。 当然,如果你只是想在自己家里科学上网,并且你的iOS设备可以和你的闲置电脑在一个网段下,那完全可以不需要这个公网v4地址。 设计思路 因为iOS本身支持的vpn协议类型有限,所以必须要搭建一个符合其所支持协议的vpn服务,并将它的流量统一转发给代理内核本身提供的的socks5,因此就可以通过连接路径粗略总结出搭建路径 iphone -> vpn服务(用docker搭建并创建专属网段) [ ->编写流量规则,在防火墙上把vpn服务接到的流量做标记 -> linux内核将做过标记的流量根据路由表发给特定网卡 -> 网卡转交给socks5 ] ->xray/v2ray/singbox提供的socks5代理 开始 先来介绍一下iOS自带的VPN功能。就拿我目前正在使用的iOS 18举例,一般来说,如果你此前从未使用过类似小火箭这样的软件,那么自带VPN入口大概率会在"设置"->“通用”->“VPN与设备管理"底下 如果你用过,那么在网络连接相关设置附近应该也能看到 在添加VPN配置中查看可添加的VPN类型,可以发现只支持这三种 其实第一种和第三种他们本质上都是第二种IPsec的"套壳”,从传输特征和加解密复杂度上看没有太大区别,只不过在验证逻辑和配置上略有不同。我们待会儿会用到第三种"L2TP" 来到我准备好的闲置linux服务器上,先配置好自己的代理内核,可以是你的clash,又或者v2ray等等,这里我就不做介绍了,直接拿准备好的代理测试一下 成功代理到了我的美中节点,那么此时我们的目标其实很清晰,就是想办法让我的iphone通过自带vpn所支持的协议与我的服务器建立链接,并将我iphone上的请求全部转交到服务器的socks5代理上 也就是我们需要搭建一个vpn服务端供我的iphone连接 实现在家可用 因为我不想脏了自己的环境,毕竟我的服务器还要跑别的东西,同时为了方便后续做流量规则,所以我选择用把vpn服务架在docker。先给我的vpn服务创建一个专属网段 docker network create --subnet=172.20.0.0/16 vpn_net 接着直接用别人包好的hwdsl2/ipsec-vpn-server镜像即可一键部署。如果对vpn有研究的观众并且用过这个镜像可能会有问为什么不设置固定的dns,以防dns泄露?其实如果不设置就可以将dns请求也一并交给代理节点,也就没有dns泄漏的问题 docker run -d --name ikev2-vpn \ --restart=always \ --privileged \ --net vpn_net \ --ip 172.20.0.2 \ --name vpn\ -p 500:500/udp \ -p 4500:4500/udp \ -e "VPN_IPSEC_PSK=预共享密钥(随便填)" \ -e "VPN_USER=用户名" \ -e "VPN_PASSWORD=密码" \ hwdsl2/ipsec-vpn-server 如果成功,你应该能在日志里看到类似这样的输出 ...

January 7, 2026 · 2 min · Red

2025 TKKCTF | WriteUp

本篇wp还总结了本届TKKCTF在使用新版docker与xinted时,在Pwn方向上题/出题阶段遇到的问题的解决方法,以及密码题Isomorphia_revenge出现非预期解的原因与解决方法,希望能给各位校外师傅将来出题提供点帮助,再次感谢所有参与并支持本届TKKCTF的参赛选手与测试人员,TKKC Sec因为有你们而变得更好~ Web Eat 2023 TKKCTF - 好吃,爱吃,多吃 Real or AI 本题灵感来源于 有很多人在群里晒自己的得分,不过这对一个训练有素的hacker来说应该想要多高就有多高 网页内容基本没有变化,先来看看怎么修改得分 最低1张图1局,我们就先随便玩一局看看显示结果的页面有什么值得注意的内容 运气不太好。此时你如果只是想到群里晒得分,可以直接修改HTML的内容 可是这样一点也不真实,左边的圆圈应该要随正确的比例填满,所以直接修改HTML的方法显然不是最好的,因此我们就要进一步看看前端还有什么别的实现 在js部分发现了这个 资源结构很简单,出了这个js外没有别的脚本,因此应该只要分析这个脚本就行。 分析过前端逆向的人应该都知道,前端逻辑代码大部分都是经过混淆或者加密处理的,这个网页也不意外,所以如果直接通过打随机断点去分析,工作量会很大。因此我们需要找到一个明显又值得关注的东西,回到刚才我们随便玩的一局里,观察到“score”这个字符串,负责表示分数,所以我们就随便搜一搜 范围一下被缩小到了6个,所以我们就只需要观察这6个地方的逻辑,应该就能找到突破口 通过观察,最后可以将目标锁定在这个结果 为什么是这里?这是6个结果中的第2个,如果我们把刚才的6个结果都看过去,可以在第6个结果中看到这样一段 通过属性名与"results"可以很大胆的猜测这段结果就是最后输出给我们看的东西,而后面这三个值被赋给了Yy,也就是6个结果中第2个结果的那个函数,所以如果能操控Yy,很大概率我们就可以控制最后的输出结果。 我没有对Yy做很细致的分析,不过粗略的看一眼也能获得证实我们上面想法的线索 如果我们在刚才随便玩的那一局里再点一下“SHARE RESULTS”按钮,会发现他会复制一段话,而这段话的片段很明显就是这几行代码组成的,因此这更加证实了Yy就是负责最后输出页面内容的函数。 而经过对这几行代码的分析也有别的发现,那就是变量o,这个变量在18193行被拿来做分数判断。回到Yy函数的变量声明里,会发现o变量就是由totalRounds和score组成的 所以思路到此就很清晰了,我们可以尝试在o变量被赋值的地方下条件断点,让o使用我们自己定义的e和t,就能达到随意控制最终输出结果的目的。 此时我们再去玩一局,就会发现成功触发断点 放行全部断点后就能得到 此时如果你只是想炫耀999的截图任务,就已经完成了。但对于CTF选手来说,走到这步没有拿到flag肯定是很不满意的。所以我们就回到CTF最原始的玩法 如果你头铁,可以一个一个搜过去也可以。但我的习惯一般是只关注前几个和后几个。前3个结果显然不是我们要找的flag,那就看看后3个 很明显这就是我们要找的东西,获得方法也很简单,e和t我们刚才都已经分析过(理论上没有走过我们刚才那些步骤的选手应该也要去了解e和t的值分别是什么,当然也不乏可以猜出来)写个计算sha256的脚本就能解决 #!/bin/bash e=999 t=999 salt="HDJackm2G7SfnAwGWXOYgHfuB1cj6DEZ" #盐是每个靶机随机生成的,需要你自己替换 msg="${salt}${e}${t}" hash=$(echo -n "$msg" | sha256sum | awk '{print $1}') curl "https://47.122.52.77:33695/secrets/$hash.txt" SecOps 访问题目,被重定向至登录页面 在登录页面可以发现一个下载接口/common/download?file= ...

December 7, 2025 · 8 min · Red

2025 TKKCTF Beginner | WriteUp

本次WriteUp为各出题人收集版,如遇问题可以找对应题目出题人 Web 彩蛋 顺便解释一下为什么是“可能见过也可能没见过” 以及为什么提示叫“挖掘xujclab.com” easy_upload wp-2.pdf ez_http 出题者 zsm 留言:第一次出web题,质量不算特别高,请见谅 源码 <?php echo "<h1>你知道http协议吗?</h1>"; echo "<h1>不知道?还不去查查!!!</h1>"; echo "<h2>你知道怎么修改请求包吗?</h2>"; echo "<!-- 你也许需要一个工具?看看web手册呢? -->"; $flag = file_get_contents('/flag'); $flags = str_split($flag, 5); if ($_SERVER['HTTP_USER_AGENT'] != 'xujcBrowser') { die('请使用xujcBrowser浏览器访问'); } echo $flags[0]; if (!isset($_GET['hello'])) { die('<br>请用GET方式传递hello=world'); } echo $flags[1]; if ($_GET['hello'] != 'world') { die("<br>hello参数不正确"); } echo $flags[2]; if (!isset($_POST['web'])) { die('<br>请用POST方式传递web=security'); } echo $flags[3]; if ($_POST['web'] != 'security') { die('<br>web参数值不正确'); } echo $flags[4]; if (!isset($_COOKIE['flag'])) { die('<br>请设置cookie flag=secret'); } echo $flags[5]; if ($_COOKIE['flag'] != 'secret') { die('<br>cookie flag值不正确'); } echo $flags[6]; if ($_SERVER['HTTP_REFERER'] != 'http://localhost:8080/') { die('<br>请从http://localhost:8080/访问'); } echo $flags[7]; if ($_SERVER['HTTP_X_FORWARDED_FOR'] != '127.0.0.1') { die('<br>请从127.0.0.1访问'); } echo $flags[8] . "<br>"; echo '<h1>看来你知道http协议了</h1>'; 首先你要去学一下http协议,以及了解一下这个包里面的内容 ...

December 6, 2025 · 18 min · Red

[靶场笔记]第二十五章

ssh -R和-D 先来说说-R 这是个很神奇的参数,有两种用法 -R 1080 -R 1080:localhost:1080 后者和-L用法是反过来的,-L是将远端端口映射到本地端口,这样在本地就能访问远端服务器的服务。-R则是将本地端口映射到远端,这样在远端就能访问本地端口的服务。 前者就厉害了,研究时被AI误导了很久 注意框起来这段话,意思就是,在不使用localhost等指定本地映射的地址和端口号时,会在本地(运行ssh指令的这台机器)自动起一个socks4/5代理服务,并且不会进行转发功能(经过测试得知)。所以如果你想要转发功能而不是让ssh帮你起一个socks4/5,就要写完整。而且ssh起的这个socks代理并不好用,会出现export https_proxy无法正常生效的问题(但proxychains可以) -D就更厉害了,如果在远端服务器上有一些需要你访问,但你又没有权限运行一些类似gost这样的代理工具,就可以用-D来搭建一个socks服务。不仅可以用-L,也可以用-D,-D的原理是在远端起一个socks4/5代理,你只需要在自己电脑上的浏览器将代理地址修改成127.0.0.1:1080,就能用ssh帮你在远端起的代理访问远端的服务,非常适合在低权限渗透环境下使用的一个参数 stripos(‘akared’,$b) 正常情况下,会返回akared所在的起始位置,没找到返回false 如果$b为数组,stripos则会返回null basename($a) 假设$a='/var/www/html/../ii.php';,会返回ii.php 而且各种截断手法在这里基本没有作用,只有一点值得关注,就是特殊字符会被它删掉,比如中文、%ff 对__PHP_Incomplete_Class_Name的补充 在靶场笔记第24章有对__PHP_Incomplete_Class的介绍,最近在CTF里碰到了,再补充一点 当我们手动为__PHP_Incomplete_Class类赋值并反序列化再序列化它时,他会将储存的内容清除。但如果我们手动赋值的是__PHP_Incomplete_Class_Name,它会将它所赋的值在serialize时将类名覆盖成我们赋的值,具体用法如下 <?php $raw = 'O:1:"A":2:{s:1:"a";s:1:"b";s:27:"__PHP_Incomplete_Class_Name";s:1:"F";}'; $exp = 'O:1:"F":1:{s:1:"a";s:1:"b";}'; var_dump(unserialize($raw)); var_dump(serialize(unserialize($raw))); // object(__PHP_Incomplete_Class)#1 (2) { ["__PHP_Incomplete_Class_Name"]=> string(1) "F" ["a"]=> string(1) "b" } string(28) "O:1:"F":1:{s:1:"a";s:1:"b";}" 这种特性就可以用来绕过下面这种情况 public $value; $ser = serialize(unserialize($this->value)); $instance = unserialize($ser); if ($ser != $this->value && $instance instanceof Access) { include($instance->getToken()); } 我们的目标是让$instance=new Access;,要达到这一点就是要让$ser='O:6:"Access:"...',但$value的值又不能和$ser一样,因此我们要找一个能经过serialize(unserialize())处理后与目的一致且字符串又不是$ser的技巧,也就是我们的__PHP_Incomplete_Class_Name 具体生成payload如下 $token = new Access(); $ser = serialize($token); $ser = str_replace('Access":2', 'LilRan":3', $ser); $ser = substr($ser, 0, -1); $ser .= 's:27:"__PHP_Incomplete_Class_Name";s:6:"Access";}'; $user->value = $ser; 这样经过两层处理后的$value不仅能达到我们目标$ser的效果,又和$ser在字符串上不一样,因此就能绕过 ...

August 18, 2025 · 1 min · Red

2024 Seccon13 Online Web | Writeup

ssrf-self 非常超模的一题,能做出来的都是狠人(当然不排除很大一部分是猜对的,实际分析起来确实复杂) 先来看代码 用bun起的服务,这个bun是个什么呢?可以理解成一种全新的nodejs,对nodejs很多功能进行了重构提升性能 非常正常的包引用。接着来看逻辑代码。 根路由判断了query是否给flag传值,如果传了才能继续跳转到其他路由。也就是说,不管访问的是什么,因为有next事件的存在,所以必须经过根路由的这个检查 携带正确的flag值访问flag路由就能得到flag。但我们肯定不知道flag是什么 最后就是ssrf路由,会把请求拿去URL类做实例化,并判断是否正确与LOCALHOST进行拼接,重新赋值pathname为flag,添加参数flag和正确的flag值,代替我们访问/flag路由。 乍一看好像直接访问/ssrf就应该返回给我们正确的flag才对,但问题就在于根路由的next,必须先赋值正确的flag值才能访问ssrf 看到这里我就在想,append是会把flag覆盖过去还是把flag变成数组添加一个值?用附件起一个服务试试 为了看到传到flag路由的具体内容是什么,我们需要修改一下代码 注意只能存在一个res.send,否则会报错 先来看看直接传flag值会发生什么 跳到了正则表达式的第二个结果里。而我们的目的肯定是第一个结果,所以来看看传进来的req.query是什么 会发现append最终是把我们的flag参数变成了数组,所以不会跳到第一个结果。 那要怎么绕过呢?猜测漏洞点应该就出在 URL对req.url的处理,可能存在什么符号或格式让URL类在过了根路径的检查后仍然认为flag这个参数不存在 bun本身自带的express框架对req.query的识别与URL对req.query的识别不一致 而两种思路也正对应了这题的两种解法 1./ssrf?flag[=] 我们在php里经常用?a[]=1&b[]=2来绕过md5比较,在js里也有类似的用法。比如我们想用url给某个数组的某个键值赋值,可以用这种方法 ?flag[1]=1 那要是我们的键值不是数字,而是别的什么呢 接下来就是想办法怎么让req.query觉得存在flag参数,又让URL觉得不存在flag参数 方法就是标题里的payload。当传进来的值是flag[=]时,req.query会觉得你在给flag的=键赋值,URL会觉得你在给flag[赋值 2./ssrf?flag\ufeff 先来看看bun带了什么模块 负责url处理的猜测是parseurl。且在express/lib/requests.js里也能看到引用 到github搜项目https://github.com/pillarjs/parseurl/blob/1.3.3/index.js 读取到URL数据后先判断是不是空的,如果不为空,将在缓存中查找是否存在(fresh方法)。如果不存在,将进入fastparse 判断url是否是字符串且以/开头。如果不是则交给bun更底层的parse函数去处理 这个parse函数又是怎么处理的呢?这就得进到bun到项目文件里一探究竟了https://github.com/oven-sh/bun/blob/bun-v1.1.36/src/node-fallbacks/url.js#L49 这一段主要就是对反斜杠进行处理,然后赋值url给rest 紧接着将rest后面的空白符删除 什么是空白符?有很多,其中有个比较不常见的叫BOM(Byte Order Mark)分隔符 接着先回到parseurl往下继续分析 如果没跳进上面的if,就会来到这个for循环。简单来说就是先以?为分隔符分出三个部分,再对各种特殊字符作处理,但有一个case格外亮眼,就是0xfeff 意思就是,如果我们的发送的数据里带了0xfeff(也就是\ufeff)也会跳进parse进行处理 而根据我们上面对parse的分析,他会将包括0xfeff在内的空白符删掉,导致在req.query做判断时会认为我们传递进来的参数就是flag而不是flag\ufeff,因此就能绕过undefined 但URL类并不会认为flag和flag\ufeff是一样的,他会认为这是两个不一样的变量,在append时就会单独起一个flag变量赋值 持续更新ing……

June 8, 2025 · 1 min · Red