strchr函数误用与CTF实战:从qwb2019_one看指针返回值陷阱
1. 从一道CTF题看strchr的真实行为1.1 这道题到底在考什么qwb2019_one 这道题我第一次看到的时候觉得挺简单——不就是个字符串处理吗结果上手之后才发现坑全在strchr这个函数的返回值判断上。题目本身逻辑不复杂给了你一段输入程序内部做了一些字符串操作最后根据操作结果决定是否给你 flag。但问题在于程序对strchr返回值的处理存在一个非常经典的误用导致攻击者可以绕过原本的检查逻辑。先说说strchr是干嘛的。C 标准库里的strchr函数原型是char *strchr(const char *s, int c);它的作用是在字符串s中查找第一次出现字符c的位置返回指向该位置的指针。如果没找到返回NULL。看起来很简单对吧但问题就出在“没找到返回 NULL”这个点上。很多人在写代码的时候会下意识地认为“没找到”就是返回 0 或者 false然后写出这样的判断if (strchr(input, x)) { // 认为找到了 x do_something(); }这段代码本身逻辑是对的——找到了返回非 NULL 指针条件为真没找到返回 NULL条件为假。但真正的坑在于当你要找的字符是\0的时候strchr会返回指向字符串结尾空字符的指针而不是 NULL。因为空字符也是字符串的一部分strchr确实“找到”了它。1.2 为什么这个误用能成为考点在 qwb2019_one 这道题里程序的核心逻辑大概是这样的读取用户输入然后检查输入中是否包含某些特定字符根据检查结果走不同的分支。其中一个关键分支用到了strchr来判断某个字符是否存在。出题人故意把要查找的字符设置成了可以通过输入控制的值或者利用了strchr对\0的特殊行为让选手可以构造出绕过检查的输入。我实测下来这类题目的核心思路通常是程序想检查输入中是否含有某个“危险字符”如果含有就拒绝。但如果这个“危险字符”恰好是\0而选手在输入中嵌入空字节strchr会返回非 NULL程序认为“找到了危险字符”从而拒绝——这看起来是安全的。但反过来如果程序逻辑是“如果没找到危险字符就放行”而选手通过某种方式让strchr返回 NULL就能绕过检查。更常见的情况是程序用strchr的返回值直接做指针运算比如strchr(input, c) - input来计算偏移。如果strchr返回 NULL这个减法就会得到一个巨大的负数或者未定义行为后续如果把这个结果当作数组索引就可能造成越界读写。注意strchr在查找\0时返回的是指向字符串结尾的指针不是 NULL。这个行为是 C 标准明确规定的不是实现差异。2. strchr 误用的几种典型场景2.1 返回值当布尔值用这是最常见的一种误用。很多人写代码时会把strchr的返回值和布尔值混为一谈if (strchr(input, A) 1) { // 错误strchr 返回的是指针不是 1 }这种写法在 64 位系统上几乎永远为假因为指针的值不可能是 1。正确的写法应该是if (strchr(input, A) ! NULL) { // 正确找到了 A }但在 qwb2019_one 里问题更隐蔽。程序可能用了类似if (!strchr(input, c))的写法来判断“没找到”然后走放行分支。如果攻击者能让c变成\0那么strchr永远返回非 NULL!strchr(...)永远为假放行分支永远走不到——这看起来是更安全了。但如果逻辑反过来程序用if (strchr(input, c))来判断“找到了就拒绝”那么当c是\0时strchr永远返回非 NULL程序永远拒绝这又会导致拒绝服务。真正的利用点在于程序可能先对输入做了一些处理比如把某个字符替换成\0然后再用strchr检查。这时候strchr的行为就会变得不可预测。2.2 指针减法计算偏移另一种典型误用是用strchr的返回值减去原始指针来计算字符位置int offset strchr(input, X) - input;如果strchr返回 NULL这个减法在 C 标准里是未定义行为。在实际运行中通常得到的是一个很大的负数因为 NULL 是 0减去一个非零指针得到负值。如果后续代码把这个 offset 当作数组下标使用就会造成越界访问。在 qwb2019_one 中我猜测出题人可能就是利用了这一点程序先计算某个字符的偏移然后根据偏移去访问一个数组或者做分支判断。如果攻击者能控制输入使得strchr返回 NULL就能让 offset 变成一个异常值从而绕过原本的边界检查。2.3 与 strlen 配合时的陷阱还有一种情况是strchr和strlen一起用char *p strchr(input, Y); if (p) { int len strlen(p); // ... }这段代码本身没问题但如果input中没有Yp就是 NULLstrlen(NULL)会直接崩溃。在 CTF 题目里这种崩溃有时候可以被利用来做信息泄露或者绕过某些检查。qwb2019_one 的考点很可能就是程序用strchr找到某个字符后直接对返回值做了解引用或者长度计算但没有检查 NULL。选手通过构造不含该字符的输入让程序崩溃或者进入异常分支从而拿到 flag。3. 复现环境与实操步骤3.1 搭建本地测试环境要复现这类问题你需要一个 Linux 环境或者 WSL安装好 gcc 和 gdb。我平时用的是 Ubuntu 20.04直接apt install gcc gdb就行。然后写一个模拟 qwb2019_one 核心逻辑的小程序#include stdio.h #include string.h int main() { char input[256]; printf(Input: ); fgets(input, sizeof(input), stdin); // 模拟题目中的检查逻辑 char *p strchr(input, A); if (p NULL) { printf(Check failed: A not found\n); return 1; } // 计算偏移 int offset p - input; printf(Offset of A: %d\n, offset); // 根据偏移做判断 if (offset 0) { printf(Flag: flag{test_flag}\n); } else { printf(Try again\n); } return 0; }编译的时候记得关闭栈保护方便观察gcc -o test test.c -fno-stack-protector -no-pie3.2 构造输入触发误用运行这个程序输入A开头的内容比如Ahello你会看到 offset 是 0程序输出 flag。但如果你输入hellostrchr返回 NULL程序直接走Check failed分支。现在关键来了如果我把要查找的字符改成\0呢修改代码char *p strchr(input, \0);这时候无论输入什么strchr都会返回指向字符串结尾的指针offset 等于strlen(input)。如果程序原本的逻辑是“offset 为 0 就放行”那么你只需要输入空字符串直接回车strlen为 0offset 就是 0直接拿到 flag。这就是strchr误用的核心当查找字符是\0时返回值永远非 NULL且偏移等于字符串长度。攻击者可以通过控制字符串长度来让偏移等于任意想要的值。3.3 用 gdb 观察返回值如果你想更深入地理解可以用 gdb 跟一下gdb ./test (gdb) break strchr (gdb) run输入内容后gdb 会停在strchr调用处。用finish命令执行完函数然后p $rax查看返回值。你会看到当查找\0时$rax指向的是输入缓冲区的末尾而不是 0。提示在 64 位系统上函数返回值放在rax寄存器里。strchr返回 NULL 时rax为 0返回有效指针时rax是一个高地址值。4. 常见问题与排查技巧4.1 为什么我的输入里没法嵌入空字节很多新手会问我想在输入里放\0但是用scanf或者fgets读入的时候空字节会被截断啊。确实标准输入函数遇到\0会当作字符串结束符。但在 CTF 题目里程序通常是用read系统调用直接读二进制数据的不会截断。如果你在本地测试时想模拟可以用 Python 的pwntoolsfrom pwn import * p process(./test) p.send(bhello\x00world\n)这样空字节就能成功发送过去。程序用read读取时会完整读到缓冲区里后续strchr查找\0时就会在hello后面那个位置返回指针。4.2 strchr 和 memchr 的区别有些人会混淆strchr和memchr。memchr的原型是void *memchr(const void *s, int c, size_t n);它会在指定的内存区域内查找字符不管里面有没有空字节。而strchr遇到空字节就停了。所以在处理二进制数据时应该用memchr而不是strchr。qwb2019_one 的误用本质上就是程序处理的是二进制数据但用了strchr这个面向字符串的函数导致空字节后面的内容被忽略或者空字节本身被特殊对待。4.3 排查这类问题的通用思路遇到疑似strchr误用的题目我一般的排查流程是这样的步骤操作目的1用 IDA 或 objdump 找到strchr调用点定位关键逻辑2查看第二个参数要查找的字符判断是否可能为\03检查返回值的使用方式看是当布尔值、做指针减法还是直接解引用4构造输入让返回值异常尝试 NULL 或指向末尾5观察程序分支变化确认是否绕过了检查这个流程在大多数涉及字符串函数的 CTF 题目里都适用。关键是要理解strchr的返回值不是一个简单的“有/无”标志而是一个指针指针的值和有效性都需要仔细处理。4.4 一个容易忽略的细节还有一个细节strchr的第二个参数是int类型但实际只用了低 8 位。如果你传一个大于 255 的值它会自动截断。比如strchr(s, 0x100 A)等价于strchr(s, A)。这个特性在某些题目里也会被利用比如程序从输入里读一个整数作为要查找的字符攻击者可以传入0x100截断后变成0也就是\0。我在实际做题时踩过这个坑当时一直想不通为什么输入256会让程序行为异常后来才反应过来256 0xFF 0strchr实际查找的是空字符。5. 防御与正确写法5.1 永远检查 NULL不管查找什么字符只要不是\0strchr都有可能返回 NULL。所以正确的写法是char *p strchr(input, c); if (p NULL) { // 处理没找到的情况 return -1; } // 安全使用 p如果确实需要查找\0那更应该明确知道返回值一定非 NULL且指向字符串末尾。这时候用strlen更直接。5.2 处理二进制数据用 memchr如果输入可能包含空字节或者需要查找空字节本身应该用memchrvoid *p memchr(buf, c, buf_len); if (p NULL) { // 没找到 }这样就不会受到空字节截断的影响行为也更可预测。5.3 指针减法前先判空如果确实需要计算偏移一定要先判空char *p strchr(input, c); if (p ! NULL) { int offset p - input; // 使用 offset }千万不要在没判空的情况下直接做减法否则就是未定义行为在不同平台上可能得到完全不同的结果。5.4 用静态分析工具辅助检查现在很多静态分析工具都能检测出这类问题比如 Coverity、Clang Static Analyzer 等。它们会标记出“返回值未检查就使用”的情况。我在写 C 代码时习惯打开-Wall -Wextra编译选项gcc 会对一些明显的误用给出警告。虽然不能覆盖所有情况但能帮你发现大部分低级错误。注意编译器警告不是万能的strchr返回 NULL 后做指针减法这种情况gcc 默认不会报警告需要靠代码审查或者专门的静态分析工具。6. 从这道题延伸出的思考qwb2019_one 虽然是一道 CTF 题但它反映的问题在实际开发中非常普遍。我见过不少真实项目里开发者用strchr检查配置项、解析命令行参数、处理协议数据然后因为没检查 NULL 导致崩溃或者逻辑绕过。尤其是在网络协议解析中如果攻击者能控制输入让strchr返回 NULL而后续代码又没处理就可能造成拒绝服务甚至更严重的问题。另一个值得注意的点是C 标准库函数的返回值语义往往比表面看起来复杂。strchr只是其中一个例子类似的还有strstr、strrchr、memchr等。理解这些函数的精确行为是写出健壮 C 代码的基本功。我个人在做代码审计时会特别关注所有返回指针的库函数调用点检查返回值是否被正确判空、是否正确处理了边界情况。这个习惯帮我发现过好几个潜在的崩溃点。对于 CTF 选手来说反过来利用这些误用往往就是拿分的关键。

相关新闻

SAP S/4HANA采购主数据与货源确定实战避坑指南

SAP S/4HANA采购主数据与货源确定实战避坑指南

简介:本资源是SAP S4 HANA采购模块(S4520)的官方级中文教程PDF,专为应用顾问、项目经理及企业最终用户设计,系统解决SAP S/4HANA环境下端到端采购业务落地难题。内容覆盖采购主数据维护(含物料主记录视图分…

2026/10/14 1:10:05 阅读更多 →
八字排盘四千年:节气计算与儒略日工程实践

八字排盘四千年:节气计算与儒略日工程实践

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

2026/10/14 1:09:04 阅读更多 →
ESMM如何解决推荐系统中的CVR预估偏差问题

ESMM如何解决推荐系统中的CVR预估偏差问题

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

2026/10/14 1:09:04 阅读更多 →

最新新闻

【Unity开发】监听Scene场景事件

【Unity开发】监听Scene场景事件

🐾 个人主页 🐾 阿松爱睡觉,横竖醒不来🏅你可以不屠龙,但不能不磨剑🗡目录一、前言二、监听方法(一)SceneManager.sceneLoaded事件(二)SceneManager.LoadScen…

2026/10/14 1:54:16 阅读更多 →
基于Springboot + vue实现的民宿在线预定平台

基于Springboot + vue实现的民宿在线预定平台

🥂(❁◡❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞💖📕🎉🔥 支持我:点赞👍收藏⭐️留言📝欢迎留言讨论🔥🔥&am…

2026/10/14 1:54:16 阅读更多 →
MuPDF JavaScript 绑定中 StructuredTextWalker 详解:遍历页面文本、图片、矢量与结构树的完整回调机制

MuPDF JavaScript 绑定中 StructuredTextWalker 详解:遍历页面文本、图片、矢量与结构树的完整回调机制

图形学图像处理 【免费下载链接】mupdf mupdf mirror 项目地址: https://gitcode.com/gh_mirrors/mu/mupdf 点击查看 免费下载 本文以 MuPDF JavaScript 绑定中的 StructuredTextWalker 接口为核心,完整讲解 StructuredText.prototype.walk() 提供的全部…

2026/10/14 1:54:16 阅读更多 →
Gym环境完全指南:核心接口、自定义环境与避坑实践

Gym环境完全指南:核心接口、自定义环境与避坑实践

很多人第一次接触强化学习,跑通第一个示例用的几乎都是Gym环境。我记得自己刚入坑那会儿,光搞明白env.step()到底该传什么、返回值又是什么,就折腾了一整天。网上教程虽然多,但大多数都在讲算法,真正把环境本身讲透的反…

2026/10/14 1:54:16 阅读更多 →
Pyenv详解:Python版本管理的核心原理与工程实践

Pyenv详解:Python版本管理的核心原理与工程实践

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

2026/10/14 1:54:15 阅读更多 →
并行编程——MPI 和OpenMPI 基本概念学习(一)

并行编程——MPI 和OpenMPI 基本概念学习(一)

文章大纲引言一、MPI 概述二、MPI基础数据类型1、使用MPI_Type_create_struct创建结构体类型数据类型1.1、在C中定义结构体并确定成员偏移量1.2、创建自定义 MPI 数据类型2、使用MPI_Type_create_subarray创建子数组类型数据类型2.1、确定子数组的形状和位置2.2、创建自定义 MP…

2026/10/14 1:53:15 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

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

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

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

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

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

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

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

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

2026/10/12 18:11:17 阅读更多 →