libc_tool.py 是个给 PWN / 本地调试用的脚本
- 给 ELF 找可能匹配的
libc - 从已有
libc.so下载整套运行库 - 按目标程序的
DT_NEEDED补缺的共享库 - 按
soname或 Debian/Ubuntu 包名补额外依赖 - 自动校验
libstdc++.so.6/libgcc_s.so.1的 ABI 版本需求
- Python 3
pwntoolspatchelf(执行patch时需要)unix_ar或系统ar;data.tar.zst还需要zstandardeu-unstrip(来自elfutils,用于把libc6-dbg/libc6-dbgsym合并成未 strip 的 libc)readelf、objdump、strings- 可选:
docker compose或docker-compose,用于一键启动容器调试环境 - 可选:Rust/Cargo,用于构建
libc_tool_core检查:
python3 libc_tool.py doctorlibc_tool.py 仍是入口,Rust core 是可选加速后端。当前迁移到 Rust 的路径:
- ELF 基础解析:build-id、架构、
DT_NEEDED、是否有.dynamic、是否有 debug section libc-database索引构建:sha1、build-id、架构、版本、常用符号 suffix 索引GLIBCXX_*/CXXABI_*/GCC_*/GLIBC_*/GLIBC_ABI_*token 提取- Debian/Ubuntu
Packagescontrol entries 扫描和候选包 URL 排序
构建:
cargo build --releasePython 会按以下顺序查找 core:
- 环境变量
LIBC_TOOL_CORE libc_tool.py同目录下的libc_tool_core- 仓库内
target/release/libc_tool_core PATH中的libc_tool_core
查看是否启用:
python3 libc_tool.py core-info如果找不到 Rust core,工具自动回退到纯 Python 实现。
重建索引时如果 Rust core 可用,会优先走 Rust:
python3 libc_tool.py rebuild-index这个脚本依赖本地 libc-database。官方仓库:
https://github.com/niklasb/libc-database
git clone https://github.com/niklasb/libc-database.git ./libc-database
cd ./libc-database
./get ubuntu如果要把本地 libc-database 也填充 Debian 条目:
./get debian脚本下载回退和依赖补全会优先使用 libc 字符串里的发行版信息,Ubuntu 使用 archive.ubuntu.com / security.ubuntu.com,Debian 使用 deb.debian.org / security.debian.org / archive.debian.org。需要换源时可设置:
export PWN_DEBIAN_ARCHIVE_URL=https://deb.debian.org/debian
export PWN_DEBIAN_SECURITY_URL=https://security.debian.org/debian-security
export PWN_DEBIAN_OLD_RELEASES_URL=https://archive.debian.org/debian
export PWN_DEBIAN_OLD_SECURITY_URL=https://archive.debian.org/debian-security
export PWN_DEBIAN_DEBUG_URL=https://deb.debian.org/debian-debug
export PWN_DEBIAN_OLD_DEBUG_URL=https://archive.debian.org/debian-debug
export PWN_UBUNTU_DDEBS_URL=https://ddebs.ubuntu.com数据库和缓存路径支持环境变量,默认会自动发现仓库旁边或 ~/CtfTools 下的数据库:
export LIBC_TOOL_DB_PATH=/path/to/libc-database/db
export LIBC_TOOL_INDEX_CACHE=/path/to/libc-database/db/.index_cache.json
export LIBC_TOOL_CORE=/path/to/libc_tool_core
export LIBC_TOOL_CACHE_DIR=/path/to/libc_tool-cache
export LIBC_TOOL_TEMPLATE_ROOT=/path/to/deploy_pwn_template所有路径会在读取时转换为绝对路径。
查 ELF 时会综合多种信号排序候选 libc:
- hash / build-id / 符号地址
- 目标架构
- Ubuntu / Debian 发行版
- GLIBC 版本
默认只显示前 20 个候选。查看所有子版本或取消数量限制:
python3 libc_tool.py find --all-variants --candidate-limit 0 ./pwn查匹配:
python3 libc_tool.py find ./pwn二级命令支持两种简写:
- 固定别名:长期稳定,推荐优先使用
- 唯一前缀:当前能唯一命中时可直接调用
固定别名:
python3 libc_tool.py f ./pwn
python3 libc_tool.py dl ./libc.so
python3 libc_tool.py pt -y ./pwn
python3 libc_tool.py rs ./pwn
python3 libc_tool.py do
python3 libc_tool.py reb
python3 libc_tool.py cc
python3 libc_tool.py ci唯一前缀示例:
python3 libc_tool.py pat -y ./pwn
python3 libc_tool.py cle
python3 libc_tool.py cor如果你只有一个 ELF,想自动选择推荐 libc 并继续后续下载流程,可以加 -y:
python3 libc_tool.py download -y ./pwn在交互终端里,如果不加 -y,libc_tool 会自动启用基于 Python prompt_toolkit 的 TUI 选择器:
- 选择候选
libc - 在
libc选择界面顶部显示当前 ELF 的路径、架构、目标 GLIBC、发行版推断和候选数量,并带颜色高亮 - 选择
docker --destroy要销毁的容器/镜像
如果你更喜欢旧的编号输入模式,可以临时关闭:
LIBC_TOOL_DISABLE_TUI=1 python3 libc_tool.py patch ./pwn如果你只有一个 ELF,并且希望自动完成“匹配 libc -> 下载运行库 -> patch ELF”,可以直接:
python3 libc_tool.py patch -y ./pwn如果你希望直接为目标 ELF 起一个容器调试环境,并把题目目录和运行库目录映射到容器里,可以直接:
python3 libc_tool.py docker -y ./pwn默认行为:
- 自动匹配并准备运行库
- 根据匹配到的
libc/ld推断尽量匹配的 Ubuntu 基础镜像版本 - 在目标 ELF 同目录下生成一套部署目录;模板来自
--template指定的仓库模板,仓库内没有模板时才回退到LIBC_TOOL_TEMPLATE_ROOT - 生成的部署目录会自带
bundle/challenge和bundle/runtime,docker-compose.yaml使用相对路径挂载;整个目录可直接复制到别的 Linux 上继续docker compose up -d --build - 默认把宿主机
10001端口映射到容器内1337 - 把部署目录里的
./bundle/challenge挂载到容器内/challenge - 把部署目录里的
./bundle/runtime挂载到容器内/runtime - 容器内直接执行目标 ELF,不再显式调用自定义
ld-linux ... --library-path ... /runtime主要作为额外共享库来源,容器会把非 glibc 核心库整理到单独目录;运行库路径只在目标题目进程启动时通过LIBC_TOOL_LIBRARY_PATH转换为LD_LIBRARY_PATH,不会污染socat、gdbserver或容器内其它工具
模板会影响生成的监听器和相关资产:默认 ubuntu+socat 使用 socat,xinetd 模板生成并复制 ctf.xinetd,ynetd 模板复制模板中的 bin/ynetd。例如:
python3 libc_tool.py docker --template ubuntu+xinetd+chroot -y --generate-only ./pwn
python3 libc_tool.py docker --template alpine+ynetd+chroot+patchelf2 -y --generate-only ./pwn只生成部署文件、不立即启动容器:
python3 libc_tool.py docker -y --generate-only ./pwn直接使用本地现成运行库目录:
python3 libc_tool.py docker --dir ./libc_dir ./pwn如果目标 ELF 同目录已经有题目提供的 libc.so.6 和 loader,可以让 Docker 优先使用这套本地运行库:
python3 libc_tool.py docker --prefer-local -y ./pwn--prefer-local 只会接受通过目标 ABI、loader 和依赖检查的本地运行库。未指定该选项时,自动模式仍按 ELF 信息从 libc 索引选择候选;如果发现可用的本地版本,会提示使用 --prefer-local。例如本地 libc 显示 Ubuntu GLIBC 2.43,而 ELF 推断目标环境为 GLIBC 2.39,两者分别表示“手头运行库版本”和“自动推断的目标环境”,不会被混为同一个候选。
如果你需要容器内额外开启 gdbserver 远程调试端口:
python3 libc_tool.py docker -y --gdbserver --gdb-port 1234 ./pwn此时:
- 服务端口仍然走
--port,默认10001 gdbserver会额外映射一个仅绑定到127.0.0.1的宿主机端口,默认1234- 部署目录里会自动生成
debug.gdb和便携启动脚本debug.sh - 宿主机可直接:
./.libc_tool_docker_pwn/debug.sh如果当前部署开启了 --gdbserver,debug.gdb 里会自动带上:
- 相对部署目录自动推导出的 ELF 路径
- 运行库搜索路径和
/challenge、/runtime的路径映射 target remote 127.0.0.1:<gdb-port>
debug.sh 会在短时间内重试 GDB 连接,失败的连接状态会先清理,适合服务端口和 gdbserver 端口存在启动竞态;等待时间可通过 LIBC_TOOL_GDB_WAIT=30 调整。生成的 GDB 脚本默认跟随父进程并自动脱离 fork/vfork 子进程,shell 执行 cat、system 等命令时不会被调试器挂住。需要专门调试子进程时,可在 GDB 中手动执行 set detach-on-fork off 和 set schedule-multiple on。socat 调试监听器保持常驻,但同时只允许一个题目客户端/调试会话,断开后可以重新连接,不会因为一次 GDB 断开而重启整个容器。
无特权容器无法调用 personality(ADDR_NO_RANDOMIZE),生成的 gdbserver 会显式使用 --no-disable-randomization,因此不会再打印 Error disabling address space randomization;调试时保留题目真实的 ASLR 行为。
如果目标 ELF 的 PT_INTERP 是相对路径(例如 libc_dir/ld-linux-x86-64.so.2),准备脚本会在容器运行目录和复制后的目标目录建立对应的 loader 链接,避免 gdbserver 启动时出现 env: ... No such file or directory。
注意:容器里的题目进程仍然是由服务端口触发的,所以通常要先让 exp 或 nc 127.0.0.1 <port> 连上服务端口,再执行 ./debug.sh。
如果默认宿主机端口已经被别的题目容器占用:
- 交互终端下会自动弹出 TUI,让你选择新的服务端口或
gdbserver端口 -y模式下会自动挑选附近的空闲端口继续启动- 也仍然可以手动显式传
--port/--gdb-port
停止并清理该 ELF 对应的容器环境和生成目录:
python3 libc_tool.py docker --down ./pwn如果你还希望把该 ELF 对应的 Docker 镜像也一起删除,可以使用:
python3 libc_tool.py docker ./pwn --destroy这会等价执行该部署目录下的:
docker compose down --rmi all --remove-orphansdocker --destroy 未指定 ELF 时会进入批量选择:TUI 中使用 Space 勾选/取消、Enter 确认;无 TUI 时可输入 0,2、1-3 或 all。使用 -y 会直接销毁发现的全部 libc_tool Docker 资源。
如果没有记住对应的 ELF 或部署目录,也可以直接:
python3 libc_tool.py docker --destroy命令会列出由 libc_tool 创建的容器和未使用镜像,交互选择后再销毁。
仓库里也提供了一个可直接运行的本地 smoke 测试目录:
make -C tests/docker_smoke smoke或:
bash tests/docker_smoke/run_smoke.sh这个测试会:
- 现场编译一个最小 ELF
- 自动从宿主机提取当前 ELF 使用的
libc.so.6和ld-linux-x86-64.so.2 - 分别验证普通
docker --generate-only和--gdbserver生成结果 - 默认把中间产物放到
/tmp,不污染仓库;如需保留,执行时加KEEP=1
自动匹配到候选 libc 后,后续下载/patch 会优先使用索引缓存里保存的包元数据,不要求 LIBC_DB_PATH 下对应的原始 .so 仍然存在;也就是说,匹配阶段和自动下载阶段都可以主要依赖索引表完成。
直接下载某个 libc 的运行库:
python3 libc_tool.py download ./libc.sodownload 会尽量下载并合并未 strip 的 libc:先拿 libc6 包,再找对应的 libc6-dbg / libc6-dbgsym 或 debuginfod 符号。若最终输出目录中没有带 .symtab / .debug_info 的 libc,会返回失败,而不是静默接受 stripped libc。
Debian/Ubuntu Packages 索引中的 SHA256 和 Size 会随候选 URL 保存。下载前校验包完整性,缓存目录只有在完成标记匹配时才会复用;没有索引元数据的本地 .url 回退源会明确提示仅进行内容哈希记录。
下载运行库并补程序依赖:
python3 libc_tool.py download --elf ./pwn ./libc.so如果目标 ELF 依赖 C++ 运行库,工具会从 ELF 的 version need 中提取并校验:
libstdc++.so.6:GLIBCXX_*、CXXABI_*libgcc_s.so.1:GCC_*
目录里已有同名库但 ABI 或运行时 libc 版本不满足时,会继续尝试其他 Debian/Ubuntu 包,避免混入宿主机过新的 libstdc++.so.6 / libgcc_s.so.1。
补一个额外库:
python3 libc_tool.py download --elf ./pwn --extra-needed libstdc++.so.6 ./libc.so按包名补 Debian/Ubuntu 包:
python3 libc_tool.py download --extra-package libseccomp2 ./libc.so手动指定 soname 到包名映射:
python3 libc_tool.py download --elf ./pwn --package-hint libssl.so.1.1=libssl1.1 ./libc.so默认输出到输入文件同目录下的 libc_dir,也可以自己指定:
python3 libc_tool.py download --output-dir ./my_libs --elf ./pwn ./libc.so下载完成后直接 patch 目标 ELF:
python3 libc_tool.py patch ./pwn --libc ./libc.so如果你本地已经有准备好的 libc_dir,不想再下载 libc,可以直接 patch:
python3 libc_tool.py patch --dir ./libc_dir ./pwn如果你本地已经有 libc.so.6,并且它同目录下还有对应的 ld-linux-x86-64.so.2 以及目标 ELF 需要的其他运行库,也可以直接给 --libc,工具会优先尝试“本地直接 patch”,不够完整时才回退到下载流程:
python3 libc_tool.py patch -y --libc ./libc.so.6 ./pwn默认 patch 模式是 rpath。如果你确实需要把主程序的 DT_NEEDED 绑定到 libc_dir 内的绝对路径,可以显式改成:
python3 libc_tool.py patch --libc ./libc.so --patch-mode replace-needed ./pwnpatch 时会:
- 为目标 ELF 生成
./pwn.bak - 默认把运行库复制到
./libc_dir.libc_tool_patched,只修改隔离副本,不改动原始运行库目录 - 生成
./pwn.libc_tool.patch.json,记录原始/patch 后 ELF 哈希、运行库路径和受管理文件 - 默认用目标 loader 执行
--list做一次依赖解析验证
只有明确指定下面的选项时才会原地修改运行库;此模式仍会创建运行库备份并由 restore 校验后回滚:
python3 libc_tool.py patch ./pwn --dir ./libc_dir --in-place-runtime恢复原始 ELF:
python3 libc_tool.py restore ./pwn重建 libc 索引缓存:
python3 libc_tool.py rebuild-index清理缓存:
python3 libc_tool.py clear-cacheclear-cache 只清理工具缓存,不删除题目目录里的 libc_dir。清理范围包括:
- pwntools cache 下的
libc_tool_extra_libs libcdb_libslibcdb_dbglibcdbLIBC_INDEX_CACHE
debug 包里的 libc 可能是 debug-only ELF,没有 .dynamic,不能直接作为运行时 libc.so.6。工具现在会:
- 要求运行时
libc.so.6必须有.dynamic - debug-only 文件另存为
*.debug - 优先用
eu-unstrip把 debug 符号合并到可运行 libc - 已合并 debug 符号的 libc 不会再被源 libc 覆盖
- 即使 debug 符号获取失败,也会继续补齐
DT_NEEDED依赖
本地运行旧 libc 程序时推荐用 loader 直接验证依赖解析:
./libc_dir/ld-linux-x86-64.so.2 --library-path ./libc_dir:. --list ./pwnpatch:
patchelf --set-interpreter "$PWD/libc_dir/ld-linux-x86-64.so.2" ./pwn
patchelf --force-rpath --set-rpath '$ORIGIN/libc_dir:$ORIGIN' ./pwn如果使用 patch,工具会自动做上面的 interpreter/RPATH 处理;仍建议在 patch 后自己再跑一次:
./libc_dir/ld-linux-x86-64.so.2 --library-path ./libc_dir:. --list ./pwn