资讯详情

资讯详情

网安复试编程Day19:进制转换、异或加密与IPv4校验实战

杭电网安复试编程准备到第19天我总算把那些看着简单、上手就错的基础题折腾明白了。如果你也在准备杭电网安复试编程或者正在纠结网安方向机试到底会考什么这篇记录应该能给你一个比较具体的参考——我会把Day19这一天的完整练习过程、题目拆解、踩坑经历都写出来顺手把这段时间梳理出的网安复试编程准备路线也放在最后。说起来网安的复试编程和其他工科专业不太一样。它不单考你会不会写代码还暗暗考察你有没有安全思维——比如数据边界、内存溢出、输入异常处理这些在普通编程题里是附加分在网安复试里可能就是核心考点。杭电的机试向来有难度刷题不能只盯着AC两个字母得想一想每道题背后到底想考什么。1. 杭电网安复试编程的真实构成环境、题型与给分逻辑在进入Day19的题目之前先把目标拆清楚。我花了两天时间翻经验帖、问学长得出一个比较可靠的结论杭电网安的复试编程环节考察范围并不是你想象的网络安全渗透工具脚本而是以基础编程能力为主、兼带安全敏感度测试。1.1 上机环境与语言选择杭电机试一般提供C/C、Java、Python三种语言环境不同年份环境版本会有差异但核心是编辑器基本是纯文本没有智能补全编译运行靠命令行或简单的IDE。这个细节很重要平时用惯了PyCharm自动补全和调试工具的人冷不丁切到Vi/Vim或记事本写代码手感会差很多。我的建议是主攻C语言或者Python选一个你最不依赖ide辅助的。C语言的好处是贴近底层网上大部分复试经验帖都是C写的遇到问题好查Python的好处是写起来快、字符串处理方便但要注意运行时间。网安方向复试时间一般是2到3小时题量在3到5道之间如果选Python写代码快的同时也可能因为解释执行慢在极限数据下失分这点需要权衡。1.2 题型分布与考察点从学长反馈和我看到的多套回忆题来看杭电网安复试编程基本围绕这几类题型类别常见出法实际考察点字符串处理字符统计、字符串移位、子串匹配指针/下标操作、边界判断进制与编码转换十进制转二进制、十六进制与字符互转位运算、取模、ASCII码数据结构和简单算法链表删除、栈和队列模拟、排序查找指针操作、结构体、递归安全编程基础简单的加密/解密、hash校验、异或操作理解算法过程、异常处理这就能看出它其实是在用一种平实的方式检验你有没有编程功底。网络安全方向强调底层理解所以C语言的指针、内存、位操作被考到的概率极大。单纯刷LeetCode偏算法题可能不够还得回归基础把课本上的经典小题写熟练。1.3 给分逻辑与隐性要求机试一般按用例给分过几个用例给几分大设计占分值不高。这里有一个很多新手忽略的点输入输出格式必须严格匹配。有时候一个多余的换行、一个多余的空格就会判错。网安方向还可能包含防御性编程的隐性要求——比如题目要求读取用户输入你得考虑输入过长怎么办、非法字符怎么办这些虽然不是显式用例但可能留下代码评审的印象分。Day19前我大致刷了排序、链表、字符串三类今天开始集中突破进制转换、位运算和安全算法这一块因为它是网安复试的特色区域普通复试经验贴讲得比较少。2. Day19的练习计划为什么把重点放在进制、异或和输入陷阱上今天没有贪多只做了三道题、复盘了五个错误。说实话比贪图一天刷十道题收获大得多。我的练习计划是按经典题变体易错点来设计的目的不是背题而是把一类知识吃透。2.1 选题背景从一道回忆题说起学长提到过一道回忆题输入一个十六进制字符串输出对应的二进制字符串要求二进制高位补0。看起来平平无奇但考场上有相当一部分人没做全对。原因很简单十六进制位数不是固定的二进制高位补0的规则要想清楚还有人忘了考虑输入里有小写字母。这种题目就是典型的基础里藏坑非常适合作为复试编程训练的切入点。Day19的第一道题就是类似题十六进制转二进制但我加了一个变体——要求过滤掉输入中的非法字符。2.2 日训练题清单及目标我把今天的四小时分成三段1小时复习位运算原理并刷一道经典题1.5小时做两道综合题1小时整理错题和总结输入陷阱。这里需要说明的是位运算和进制转换是后续做加密、CRC、权限控制代码的基础网安复试若涉及简单加解密底子全在这。具体题目十六进制转二进制过滤非法字符版。简单异或加密/解密输入一段明文用给定密钥做异或再输出密文再写反向解密。判断一个字符串是否为合法IPv4地址输出合法/非法及非法原因摘要。这三道题覆盖了进制转换、位运算、字符串分割与校验三个高频考点而且在网安场景里都是活知识。做题时我全程模拟机试环境——没有断点调试靠printf打印中间变量并且严格注意输出格式。2.3 设计变体的用意做完基础题我给第一题加了一个改动原始输入可能包含0x前缀也可能包含空格和无效字符需要输出清洗后的二进制串。这个变体不是拍脑袋它的价值在于网安方向日常处理数据时输入往往是不干净的你必须写代码去容错。一个合格的网安从业者读入数据时第一反应就应该是这个输入能害死我吗。复试虽然不会考渗透但考这种容错逻辑很常见它是安全意识的编程化体现。3. 三道典型机试编程题的完整解析与踩坑过程下面把Day19的三道题具体拆开像我考场上一步一步推演那样。代码用C语言写因为C在机试里最稳妥能体现指针和位运算功底。如果你选Python思路一致代码风格可以更简洁。3.1 题一带容错的十六进制转二进制题目要求输入一行字符串可能包含0x前缀、空格、字母a-f大小写、数字0-9以及非法字符。要求输出这些合法十六进制字符对应的二进制字符串每个十六进制字符转换为4位二进制高位补零。遇到非法字符忽略。输入示例0x1A z!2f合法字符序列1 A 2 f输出0001 1010 0010 1111去掉空格连起来就是0001101000101111。先说思路。十六进制字符转二进制最直白的办法是查表也可以先转十进制整数再转二进制。但这里要求每个字符单独变4位二进制查表法最简单#include stdio.h #include string.h #include ctype.h const char* hex_to_bin(char c) { switch (toupper(c)) { case 0: return 0000; case 1: return 0001; case 2: return 0010; case 3: return 0011; case 4: return 0100; case 5: return 0101; case 6: return 0110; case 7: return 0111; case 8: return 1000; case 9: return 1001; case A: return 1010; case B: return 1011; case C: return 1100; case D: return 1101; case E: return 1110; case F: return 1111; default: return NULL; } } int main() { char s[1000]; fgets(s, sizeof(s), stdin); // 去掉换行符 s[strcspn(s, \n)] 0; int len strlen(s); int first 1; for (int i 0; i len; i) { if (i 0 s[i] 0 (s[i1] x || s[i1] X)) { i; // 跳过0x前缀 continue; } const char* bin hex_to_bin(s[i]); if (bin ! NULL) { if (!first) putchar( ); printf(%s, bin); first 0; } } if (first) printf((empty)); putchar(\n); return 0; }这段代码有一个关键点判断非法字符时直接用hex_to_bin返回值是否为NULL避免写复杂条件判断。不过这里有个坑检查0x前缀时我直接访问s[i1]如果字符串是“0”长度1且只有一个0s[i1]其实是字符串结束符\0不会越界但逻辑上会误判吗不会因为循环条件i leni最大是0判断时s[1]等于\0既不等于x也不等于X所以不会跳过。这个习惯好但我得承认第一次写的时候没有检查边界后来才想到。再说我踩的第二个坑输出格式。题目要求“每个十六进制字符转换为4位二进制”如果原题没要求空格你就绝不能输出空格。我写代码时先输出了空格方便自己看结果在自测时发现和预期输出不一样调了半天才发现是格式问题。考场上一旦输出格式不符用例全错。3.2 题二异或加密与解密异或运算是网安方向最基础也最常用的运算。加密原理很简单明文与密钥逐位异或得到密文密文再与同一个密钥异或又得到明文。在C语言里就是对字节做^操作。题目设计读入一个字符串和单字节密钥key输出每个字符与key异或后的十六进制表示大写然后程序再次读入这串十六进制密文和相同key恢复原字符串。这个题目比第一题绕因为它必须自己实现字节与十六进制字符串互转。我先只写了加密部分#include stdio.h #include string.h int main() { char plain[1000]; int key; fgets(plain, sizeof(plain), stdin); scanf(%d, key); plain[strcspn(plain, \n)] 0; for (int i 0; plain[i] ! \0; i) { unsigned char c (unsigned char)plain[i]; unsigned char enc c ^ (unsigned char)key; printf(%02X, enc); } printf(\n); return 0; }在这里我踩了一个特别典型的坑%02X输出的是大写十六进制但char类型如果直接参与位运算可能因为符号位扩展出问题。我一开始写的是printf(%02X, plain[i] ^ key);当plain[i]是一个ASCII码大于127的字符比如中文或扩展字符时它可能是带符号的负数异或后按整型提升高位会补1导致输出变成FFFFFF80这样的8位十六进制。虽然考试用例大概率是纯英文但万一遇到扩展字符这就是失分点。所以要用unsigned char强制转换。这个坑非常隐蔽如果你写Python就不会遇到但C语言机试里很常见。解密部分本质是从十六进制字符串读取两位转成一个字节再异或key。要注意的坑是每个密文字符恰好是两位十六进制所以循环步长是2。代码#include stdio.h int hex_char_to_val(char c) { if (c 0 c 9) return c - 0; if (c A c F) return c - A 10; if (c a c f) return c - a 10; return -1; } int main() { char hex[2000]; int key; fgets(hex, sizeof(hex), stdin); scanf(%d, key); hex[strcspn(hex, \n)] 0; int len strlen(hex); for (int i 0; i 1 len; i 2) { int high hex_char_to_val(hex[i]); int low hex_char_to_val(hex[i1]); if (high 0 || low 0) continue; unsigned char enc (unsigned char)((high 4) | low); unsigned char dec enc ^ (unsigned char)key; putchar(dec); } putchar(\n); return 0; }这道题最值得复盘的是位运算优先级的问题。(high 4) | low这里移位运算优先级比按位或高所以(high 4) | low不会写成high (4 | low)吗实际上优先级确实高于|但我写了括号保险。机试里凡是混合位运算全部加括号别吝啬这是减少低级错误的最好习惯。另外一个容易出错的点明文中可能有换行符如果你用gets读换行符不会包含在内而用fgets会读进来所以要去掉换行。我原先想直接用gets但现在的机试环境有的编译器会警告甚至禁止gets所以建议都用fgets。3.3 题三合法IPv4地址判断这道题是我给自己加的安全校验训练。网安方向经常要处理IP地址写这类判断逻辑很能体现编程基本功。题目要求输入一个字符串判断它是否是合法的IPv4地址。合法条件是分成4段每段是0-255的十进制数字不能有前导零除了单个0不能包含空格和其他字符。示例192.168.1.1合法192.168.01.1非法前导零256.1.1.1非法1.2.3.4.5非法.1.2.3非法。我一开始想当然地用sscanf去解析比如sscanf(s, %d.%d.%d.%d, a,b,c,d)但是有个致命问题sscanf会把1.2.3.4abc也解析成功后面多的字符它不管。而且对192.168.01.1这种前导零情况sscanf也能解析成数字但它其实非法。所以必须逐字符扫描自己判断。我的最终思路是用strtok按点分割但strtok会修改原字符串且连续两个点会跳过空段容易误判所以还是手写。手写解析函数用两个指针start指向每段开始end向后扫描直到点或结尾。每段检查长度为1到3每个字符都是数字若长度大于1则首字符不能是0转成整数后判断≤255。统计段的个数必须正好是4。最后检查整个字符串没有多余字符比如开头结尾不能有空白和点。下面是我的代码简化版#include stdio.h #include string.h #include ctype.h int is_valid_part(const char* start, const char* end) { int len (int)(end - start); if (len 1 || len 3) return 0; if (len 1 start[0] 0) return 0; // 前导零非法 int sum 0; for (int i 0; i len; i) { if (!isdigit(start[i])) return 0; sum sum * 10 (start[i] - 0); } return sum 255; } int main() { char s[1000]; fgets(s, sizeof(s), stdin); s[strcspn(s, \n)] 0; int len strlen(s); if (len 0) { printf(invalid\n); return 0; } const char* p s; int dots 0; while (*p) { if (*p .) { dots; p; } else { const char* start p; while (*p *p ! .) p; if (!is_valid_part(start, p)) { printf(invalid\n); return 0; } } } if (dots 3 s[0] ! . s[len-1] ! .) { printf(valid\n); } else { printf(invalid\n); } return 0; } }这段代码我自测了几个用例发现一个隐藏bugwhile (*p)循环里遇到点就dots然后p。但如果是连续两个点192.168..1处理完第一个点后p指向第二个点下一个循环还是点dots再然后p指向1此时is_valid_part会解析“1”但中间缺少一段最终dots3s开头结尾也不是点结果竟然被判valid了。这是严重逻辑漏洞。所以不能简单统计dots数量应该按段-点-段-点-段-点-段严格结构扫描。正确做法是用parse_part读取一段并紧跟着判断其后是否为点循环4次最后一次点的出现次数应为0。我重新写了#include stdio.h #include string.h #include ctype.h int is_valid_part(const char* start, const char* end) { int len (int)(end - start); if (len 1 || len 3) return 0; if (len 1 start[0] 0) return 0; int sum 0; for (int i 0; i len; i) { if (!isdigit(start[i])) return 0; sum sum * 10 (start[i] - 0); } return sum 255; } int main() { char s[1000]; fgets(s, sizeof(s), stdin); s[strcspn(s, \n)] 0; const char* p s; int seg_count 0; for (int i 0; i 4; i) { const char* start p; while (*p *p ! .) p; if (!is_valid_part(start, p)) { printf(invalid\n); return 0; } seg_count; if (i 3) { if (*p ! .) { printf(invalid\n); return 0; } p; // 跳过点 } } // 此时p应该已经指向末尾 if (*p ! \0 || seg_count ! 4) { printf(invalid\n); return 0; } printf(valid\n); return 0; }这个写法用循环次数固定为4次每次解析一段再强制要求前3段后面必须有点第4段后面必须没有点。这样连续两个点的情况第一段后面是点第二段是空段is_valid_part(start,p)中start和p相等len0直接返回0判断为非法。这才是正确的。为什么我第一版会写错因为我跳过了“每一段后面跟什么符号”的结构检查只盯着点的个数。这类经验特别适合复试场景字符串合法性判断一定要把分隔符的出现时机一起约束好而不是简单统计。3.4 三道题之外的边角料输入读取的“第一道防线”今天这三道题集中暴露了我一个老毛病对输入读取的防御不足。比如使用fgets后一定要处理末尾换行用scanf(%d, key)后如果接下来还要用fgets读字符串缓冲区会遗留换行符导致fgets直接读到空行。这个问题几乎每次都会在别人身上看到。我在做第二题时就是先fgets后scanf顺序对了就没事但如果反过来就要用getchar()吃掉多余换行或者重新组织输入顺序。对机试而言最简单的策略是统一使用fgets一行一行读然后用sscanf或手动解析。这样可以把换行、空格都控制住虽然代码多几行但稳定性高很多。比如第二题可以先用fgets读明文再fgets读密钥再用atoi转换这样就不会有缓冲区错乱问题。4. 机试实战中的高频踩坑清单今天我踩了五个你提前避雷今天犯了五个错都记下来。这些错误不是“不会写”而是“不细心”放在考场上特别可惜。4.1 坑一输出格式里的空格与换行第一题我多输出了空格第三题我少判断了结尾。机试判题通常用diff逐字符比对多出来的空格会和答案不匹配。解决办法是看清题目的输出样例如果样例里的二进制串之间用了空格那必须有如果连在一起就不能有。拿不准时尽量按最简格式输出不要自己加易读性修饰。我第一题代码里保留了空格是为了自己看方便但最终提交前必须把空格逻辑删掉。4.2 坑二有符号char参与位运算第二题里plain[i] ^ key在没有unsigned char强制转换时若plain[i]负数位扩展会污染结果。这个坑特别推荐所有用C语言机试的同学留意。处理方法是涉及位运算的字符变量一律转成unsigned char再操作。记住一个原则C语言中char到底是有符号还是无符号由实现定义很多OJ环境里它就是有符号的。所以不要赌环境显式转换才是王道。4.3 坑三字符串结束符与数组越界我在检查0x前缀时写了if (s[i] 0 (s[i1] x ...))只要i1在字符串长度范围内就不会越界。但如果我在一个循环里频繁访问i1就要留意循环终止条件。今天复盘时我在第三题重写版本里也有类似访问p是否越界的问题因为while (*p)会确保p指向有效字符或\0不会越界但如果你使用p[i1]这种形式就要额外小心。4.4 坑四strtok的副作用我在做IPv4判断时一开始想过用strtok但它会把连续分隔符视为一个还会修改原字符串、在内部维护静态指针不适合多次调用。网安复试可能会遇到类似按逗号解析CSV按空格解析命令的题目如果你只用strtok遇到空字段就会出问题。手写解析虽然笨但可控。4.5 坑五只跑题面样例就提交很多题目题面给的样例非常温和比如十六进制只有大写、IP地址没有连续点等一旦你只跑过题面样例就会错过边界。今天我每个题都额外构造了至少5组用例空输入、全非法、首位是点、末尾是点、连续点、大写小写混合、最大数字255和256、前导零。在复试机试里边界用例往往就是区分及格和优秀的线。可以自己准备几个固定用例形成肌肉记忆每题跑完先来一遍边界套餐。4.6 一个额外的提分习惯写代码时顺带写注释机试大多不要求提交注释但如果题目有文档评审或面试官看你代码注释会大大加分。更重要的是写注释能强迫自己理清思路。比如第三题我写// 前3段后必须有点第4段后必须无点写着写着就发现了第一版的结构漏洞。注释不是给评委看的是给你自己的思维上保险。5. 网安复试编程的准备路线从Day1到Day19的复盘与延伸前19天我大致是按这个路线走的分享出来供参考。这条路线既考虑了杭电的机试风格也覆盖了网安方向可能涉及的基础。5.1 第一个周期Day1-Day6C语言地基与编译调试前6天我把C语言最核心的语法过了一遍重点不是学会而是熟练。每天写至少5个程序数组排序、链表插入删除、字符串拷贝、逆序、查找子串、二进制文件读写。这个阶段必须做到不查手册能流畅写出结构体和指针操作。杭电网安复试编程最怕的不是题目难而是你连链表节点定义都要想半天。我还有一个训练方法不开IDE自动补全只在vim里写编译报错后自己看代码找出错因。一开始很痛苦但几天后对语法和常见报错形成了条件反射。5.2 第二个周期Day7-Day12数据结构与常见算法这个阶段练了栈、队列、链表、二叉树基础遍历以及排序、二分查找、递归回溯。网安复试很少考动态规划、图论这种难题但基础数据结构出现的频率很高。比如用两个栈实现队列判断链表是否有环这类题可以作为复试练习的一档难度。这个阶段我用的是LeetCode入门题教材课后题每天控制在3道左右但每道题都要分析时间和空间复杂度。Day12那天我做了个小总结发现链表操作是我的薄弱点于是后两天专门补了链表专项。复试准备不是均匀用力而是针对薄弱点突击这个道理大家都懂但执行起来容易陷入手到擒来题刷爽的舒适区。5.3 第三个周期Day13-Day18安全相关编程基础网安复试和普通机试的区别在这个阶段体现出来。我开始练习十六进制与字节流互转、异或加密、简单的CRC校验、Base64编码、字符串哈希MD5的简化模拟、大小端转换等。这些题目技术含量不算高但会强制你用位运算、用指针、用unsigned char去处理字节同时让你不知不觉地形成底层数据运算的安全思维。Day15我做了一道模拟TCP校验和的题就是把数据按16位分组求和再取反。这道题非常综合既考了位运算、无符号溢出又和网络安全的完整性校验相关。当时我踩了溢出截断的坑后来想明白校验和计算时进位要循环回加和普通求和使用不同的逻辑。这种题如果没练过上考场会觉得摸不着头脑练过以后再遇到同类题就有脉络了。5.4 Day19及以后综合模拟与节奏训练像Day19这样用三到四道涵盖进制、位运算、字符串校验的题做一次完整模拟是非常好的节奏。后面我打算每两天做一次90分钟的模拟机试尽量为自己创造考试环境不查资料、不做多余调试、快速写、快速验。模拟时故意用一些第三方数据比如从日志文件里截取原始数据锻炼自己从不干净的输入中提取有效信息的能力。再往后要补充的点包括多线程或异步编程思想——网安方向可能会涉及并发请求或数据包并行处理但注意复试编程机试大概率不会直接考多线程因为环境很难保证公平所以只要理解基本概念即可。热搜里提到的MapReduce、HDFS编程实践如果是大数据方向复试可能有用但对网安复试来说不是核心。网安的核心还是C/Python基本功、算法基础、密码学/编码的简单实现。学习路线方面不建议一上来就啃渗透测试工具复试编程考的是基本功先把手写代码能力提上来后面学工具才有底子。5.5 给不同基础的同学的调整建议如果C语言还停留在看懂了但写不出来阶段别急着刷题先每天手写10个小函数比如字符串长度、字符串拷贝、字符大小写转换、十进制转二进制。这些写顺了后面才谈得上结构体、链表。如果已经能流畅写基础题可以尝试把每个题改造成更严格的版本比如给字符串处理题加上“输入可能包含非法字符”的前提条件锻炼容错能力。如果追求高分一定注意代码风格和边界处理杭电机试成绩有时候零点几分就能拉开名次边界用例尤其关键。写在最后Day19的一点实操心得今天最大的收获不是把三道题做出来而是意识到能做出来和稳定地做出来之间有巨大的鸿沟。第一版IPv4判断看起来逻辑自洽实际却漏了连续点的情况异或加密一开始也没关注符号扩展。这些问题靠“看着对”是不行的必须靠测试用例、靠边界思维、靠事后复盘去堵住。另一个体会是刷网安复试编程题不必贪多求快。Day19我全程只练了三道题但每道题都做了变体、加了限制还认真整理了错因。如果你离复试还有一段时间与其每天刷十道简单题不如每天吃透三到五道有代表性的题把输入输出、边界、位运算、指针这些基本功磨扎实。最后再分享一个小技巧准备一个属于自己的“边界用例测试集”每次写完程序先跑一遍。我的测试集包括空字符串、超长字符串、只有前缀没有数字、连续分隔符、最大值边界、最小值边界、负数、大小写混合、末尾换行符、带空格字符。花两分钟跑完这些至少能挡住一大半低级失分点。别嫌麻烦机试考场上一次语法错误、一次格式不匹配比一道题不会做更让人懊恼。Day20我会开始练数据包解析类的模拟题到时候继续记录。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →