ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CTF逆向实战:快速识别RC4、TEA、Base64魔改变种

CTF逆向实战:快速识别RC4、TEA、Base64魔改变种 1. 先弄清楚一件事为什么CTF里翻来覆去就这三样做CTF逆向也有一阵子了每次看到题目里出现RC4、TEA、Base64这三兄弟我的第一反应不是又来了而是终于来了。原因很简单这三类算法几乎覆盖了CTF逆向中80%的加密场景——不是出题人偷懒而是它们本身就具备极强的实战属性实现简单、形态多样、魔改空间大。尤其是近两年的比赛出题人已经很少直接上原版算法了几乎清一色是标准算法换皮或者混合套壳。你在题目里遇到的RC4大概率不是教科书上那个RC4你看到的Base64也很可能是一张被打乱的64字符表。所以这篇文章我想聊一个非常具体的问题拿到一个未知二进制怎么在现场快速判断它用的是不是RC4、TEA或Base64的变种更重要的是怎么识别出魔改点把变异还原成标准从而写出对应的解密脚本。先说一个容易被新手忽略的事实这三类算法的魔改往往不是推翻重来而是在标准实现上做局部改动。改动点通常集中在这几个位置——常量、轮数、表结构、索引运算、加解密顺序。你只要能找到标准算法中那几个打死都不会变的特征锚点再顺着锚点去对比魔改位置识别难度会大幅降低。这篇文章的整个思路就是围绕特征锚点对比法展开的。我会从标准算法的最强特征入手逐个拆RC4、TEA、Base64的识别方法然后给一个综合样本的实战案例。内容偏向现场实战适合正在刷CTF逆向题、卡在不知道用了什么算法这个环节的朋友。如果你是刚入门也能通过这篇文章建立一套系统的特征识别框架——这比死记硬背某个题目的writeup有用得多。2. RC4魔改先认出那个被玩坏了的S盒RC4在CTF逆向里的地位大概相当于数据结构里的链表——看着简单但几乎所有人都拿它出过题。原因在于RC4的核心结构实在是太显眼了而且实现自由度极高。出题人稍微改几个地方题目难度直接从签到题跳到中等偏上。要快速识别RC4你必须先对它的三个固定阶段烂熟于心。2.1 标准RC4的三段式特征标准的RC4由KSA密钥调度算法和PRGA伪随机数生成算法两部分组成整体逻辑可以用三句话概括初始化一个256字节的S盒S[i] i。用密钥K反复搅动S盒这一步是KSA。每次从S盒中取出两个索引i、j交换S[i]和S[j]拼接输出密钥流这一步是PRGA。在逆向视角里RC4最显眼的静态特征就是这个S盒初始化。你在IDA或者Ghidra里看到一个类似于vector或者数组被填成0到255的循环后面紧跟一个又长又绕的密钥搅动循环基本就可以锁定RC4了。我给你一个更具体的判断标准。在IDA的伪代码视图中RC4的标准形态是这个样子的// KSA for (i 0; i 256; i) S[i] i; j 0; for (i 0; i 256; i) { j (j S[i] key[i % key_len]) 0xFF; swap(S[i], S[j]); } // PRGA i 0; j 0; for (k 0; k data_len; k) { i (i 1) 0xFF; j (j S[i]) 0xFF; swap(S[i], S[j]); keystream[k] S[(S[i] S[j]) 0xFF]; }注意这里面的几个不可动摇的特征( 0xFF)操作暗示字节级运算S盒大小是256交换动作频繁出现密钥流是逐字节生成的。这三者只要凑齐两个先按RC4的思路去验证命中率极高。2.2 高频魔改点与识别信号魔改RC4的出题人通常在以下几个位置动刀第一类S盒长度变化。标准RC4是256字节的S盒。有的变种把S盒改成128字节、512字节甚至更大的数组。这种改动直接影响的就是循环上界和掩码常量。你在逆向时如果看到一个循环上界不是256、但结构非常类似RC4的函数记得留意它是不是压缩版或扩容版的RC4。识别时不要死盯数字256要看S[i]i初始化 双层搅动 索引交换这个结构。第二类KSA搅动次数变化。标准的KSA对S盒只搅动一遍i从0到255。有些魔改版本会让i循环两遍、四遍或者对S盒中的某一段单独搅动。这种改动的结果是同一轮KSA的时间明显变长代码里会出现嵌套循环的外层循环次数不是1。第三类密钥流的后处理。有的题目会在PRGA生成密钥流之后对密钥流做一个额外的异或或者加减再和明文进行加密。这种情况下你光分析出RC4还不够还要追踪生成后的密钥流是否被二次处理过。用动态调试去看的话这种后处理一般会在密钥流数组生成后多出几个明显的异或指令。第四类索引更新顺序变化。标准RC4是先更新i和j再交换最后取S[S[i]S[j]]。有题会调整这个顺序例如先取下标值再去更新j效果是密钥流序列完全错误但代码形态仍然非常接近RC4。这种变种最难识别因为只在指令顺序上有差异静态看的相似度可以到95%以上。2.3 实操用特征码快速圈定RC4我处理RC4魔改题的习惯是先不看加密函数直接在二进制里搜特征常量。如果打开IDA字符串窗口看到一个256字节的表被初始化成0到255或者看到一个可疑的大数组在函数入口处被循环赋值我会先围住这块区域再看上下文。再给一个更直接的技巧动态调试时直接看函数里有没有交换两个字节的密集操作。RC4的代码特征是频繁的swap而一般算法比如AES、DES的查表和置换逻辑看起来更像大块数据搬移。你用调试器随便断点几步如果发现寄存器在两个内存地址之间来回倒腾基本就是在做S盒交换这时候停下手上的活好好分析一下这个交换循环是不是RC4。我来拉一个RC4识别速查表方便现场对照特征项标准RC4信号魔改常见变化S盒大小256字节初始化S[i]i128、512或非连续初始化掩码操作 0xFF8位索引掩码位数随S盒长度变化KSA循环次数1次完整搅动多次搅动、局部搅动密钥流生成S[(S[i]S[j]) 0xFF]增加异或、加值、查表后处理交换操作swap(S[i], S[j]) 密集出现交换条件复杂化、增加计数变量如果你看到某个函数同时命中表初始化和频繁交换两项不要犹豫直接按RC4去还原。哪怕最后发现不是RC4按这个假设去推导加密逻辑也会比漫无目的地翻代码快得多。3. TEA魔改一眼锁定0x9E3779B9如果说RC4是靠结构显眼来识别那TEA系列就是靠常量杀人来识别。TEATiny Encryption Algorithm及其变种XTEA、XXTEA在CTF中出现的频率极高原因也很直接实现紧凑、代码量小、可魔改的参数多而且很多出题人习惯把TEA作为两层加密的底层引擎外面再套一层Base64或RC4。3.1 标准TEA的运算指纹标准TEA的加密核心是一个迭代结构对64比特明文分组成两个32位块进行处理每一轮的关键操作是左移4位、右移5位、加上一个固定delta常量然后与密钥进行异或和加法混合。而delta的值是TEA最出名的一个指纹。delta 0x9E3779B9这是基于黄金分割率导出的常量。只要在二进制中看到这个数几乎可以当场判定为TEA系列算法。大多数情况下一个CTF二进制里出现0x9E3779B9别管它是加密还是解密几乎可以当场判定为TEA系列算法。但同样地出题人也知道这个常量太显眼于是出现了大量换delta的魔改版本。我给新手一个建议看到0x9E3779B9先按TEA分析同时立刻检查它周围的代码结构。因为魔改TEA即便换了delta运算结构往往依然保留了TEA最核心的左移4右移5 异或 加法组合。这个组合在任何标准对称密码里都不常见它是TEA的身份证明。3.2 TEA家族与常见魔改TEA家族包含了原版TEA、XTEA、XXTEA三种主要形态它们的结构有微妙差异对应的还原思路也不一样。一起列表对比一下算法分组轮函数特征典型常量识别锚点TEA64位2×32左移4、右移5、delta参与0x9E3779B9delta出现一次密钥直接参与每轮运算XTEA64位2×32左移4、右移5、异或后加减0x9E3779B9delta参与方式不同多出sum变量XXTEA可变长块复杂混合运算不止左右两个32位0x9E3779B9出现3个以上32位块参与的循环魔改的常见方向其实就两板斧改常量、改运算。具体来说改delta这是最简单粗暴的魔改。出题人将0x9E3779B9替换成一个类似但不同的值或者用动态计算的随机数替代。识别方法就是即使看不到标准delta只要运算结构仍然满足左移4右移5 异或 加的组合就优先假设它是TEA变种然后通过运算逆向推导delta。推导delta的方法一般是找到sum变量或者密钥调度过程中的关键累加值直接运行一次加密流程用调试器看一下第一轮sum的值即可。改移位数将左移4和右移5改成左移3、右移6之类的。魔改后的代码结构几乎不变但解密脚本也必须同步调整移位。所以在识别出TEA之后要仔细确认移位指令的操作数不能直接套标准脚本。改轮数标准TEA是32轮或者64轮视实现而定有的变种改成8轮、16轮。轮数少了整个加密的混淆程度降低但这不影响识别反而让分析更容易。轮数在静态代码中通常体现为一个循环次数的比较指令动态调试时可以直接观察。3.3 实操识别XTEA/XXTEA/魔改TEA如果题目里的算法是XTEA或XXTEA识别路径会稍有不同。XTEA的代码特征里sum变量从0开始每轮累加delta而且密钥的使用方式比TEA更多样会根据sum的值选择不同密钥段。你在伪代码里看到类似(sum 11) 3这样的索引运算就是XTEA的标志性代码片段。XXTEA的特征更明显它处理的分组大小是可变的会出现n数据块数参与循环控制函数参数也会多出几个长度相关的变量整体复杂度比前两者高出一个量级。处理TEA系列时我强烈建议用动态调试辅助静态分析。原因在于TEA的加解密过程非常规整你可以用调试器在循环入口处断点直接读取sum值、左右两个分组的寄存器值一次性确认delta和轮数比盯着伪代码猜要高效得多。调试器断点确认了参数之后剩下的事就是改一个标准TEA脚本的常量几乎不存在无效工作。另外一个TEA系列的Tips如果你确认了算法是TEA但解密结果不对先不要怀疑算法识别错误而是检查数据是否经过了端序转换或分组错位。TEA的魔改题通常会在加密前对明文做块倒序、字节序调整甚至只对部分分组加密。这种情况下算法本身是对的但数据的组织方式出了问题。我在实战中遇到至少三次类似情况——算法识别完全正确最后解不出flag是因为分组轮数少了一组或者数据被按64位反序了。4. Base64魔改别被自定义表骗了Base64大概是CTF里最容易被低估的算法。很多新手觉得Base64算什么逆向一条命令就能解。但出题人深知这一点所以近年来的Base64题目已经很少老老实实用标准表了。魔改版Base64的核心玩法就一个字——表。而且因为Base64表是一个64字符的可见字符串它在内存中极其显眼只要能找到表整个算法基本就透明了。4.1 标准Base64的两个硬特征第一个硬特征是64字符表。标准Base64的表是ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/一共64个可见ASCII字符。在二进制中搜索这段字符串如果完整命中那你遇到的就是标准Base64。即使不是完整命中只要看到一个连续存放的64字符ASCII字符串其中包含大小写字母、数字和两个特殊符号基本就可以怀疑是Base64变种。第二个硬特征是3字节到4字符的转换逻辑。Base64的编码过程是将3个字节拆成4个6位索引再通过查表得到4个字符。逆向时对应到代码层面就是连续的移位运算和查表逻辑。识别标准Base64代码特征本质上非常一致3个输入字节被拼接成24位整数再按6位一组切分4次每次取出的6位作为表索引。你需要留意的是字符串表和查表逻辑的总长度会固定为64个字符。这是Base64魔改的表征锚点。4.2 自定义表的识别路径自定义表是Base64魔改中最常见的操作。出题人通常会保留标准Base64的编码逻辑但把表替换成一个随机排列的64字符序列。这样标准Base64解码工具直接解出来的是一堆乱码。但识别自定义表的难度其实不高关键路径如下在IDA的字符串列表里搜索所有长度为64、只包含可见字符的ASCII字符串。大多数情况下这个字符串就是自定义表。如果出题人没有把表放进字符串区而是在运行时动态生成你需要对关键函数下断点在内存中dump这块数据。找到表之后你要确认编码逻辑是否和标准Base64完全一致。具体做法是找一个已知明文比如ABC用程序加密得到密文然后手动按标准Base64的规则推一遍。如果结果和标准表不一致但逻辑结构相同那就是自定义表无疑了。有些魔改会在Base64编码前后加额外步骤比如先把明文的每个字节异或一个固定值再做Base64或者先Base64解码再对结果做RC4。这种情况只要我们找到了Base64表可以先把外层剥离再单独处理内层。多数情况下自定义表的题目核心就是换表这一件事后续的解密逻辑并不会复杂到哪去。4.3 实操从字符串定位魔改表我直接给一个自己常用的操作流程。假设已经拿到一个ELF或exe文件打开后先在strings窗口里搜ABCDEFG或者abcdefghijklmnop这些标准表前缀。搜不到说明表被魔改或动态生成了搜到了立刻跟踪这个字符串的交叉引用看它被传入哪个函数。这个函数就是encode或decode的核心逻辑。还有一个经验Base64表在内存中通常是连续存放的而且紧跟着一个长度为64的索引数组或者填充字符变量标准Base64的填充符是魔改版可能换成别的字符。你如果看到一个字符串长度够64末尾还跟着一个单字符变量那基本可以断定这个字符串是Base64表那个单字符是填充符。我之前碰到过一道比较隐蔽的题出题人把Base64自定义表藏在了一个不显眼的全局数组里没有放进字符串段而是用连续的0x41、0x42这种字节硬编码出来。一开始用strings完全扫不到后来是通过动态调试在加密函数内部发现一个查表循环断点读内存才dump出来的。所以识别Base64的完整思路应该是静态搜字符串加动态dump内存双管齐下缺一个都可能漏。识别步骤静态分析动态分析说明找表搜索64字符ASCII串函数断点dump内存标准表前缀搜索命中率最高查逻辑观察3字节→4字符的移位查表循环用已知明文测试加密确认逻辑未被改动查填充搜索或自定义填充符观察尾部填充处理填充符改动会直接影响解码结果查套壳观察编码前后是否有异或等操作逐字节跟踪输入输出剥离外壳后再做Base64处理5. 综合实战一个三重加密样本的识别全过程理论说了不少现在走一遍实战流程。假设我们拿到的是一个Linux ELF的reversing题目运行之后提示输入flag输错就直接退出。打开IDA一眼扫过去main函数里有一大串代码看起来并不是简单地strcmp验证而是先对输入做了一大堆操作再与某个固定的密文比较。现在目标就是搞清楚这一大堆操作是什么。5.1 拿到样本先干什么我不会一上来就进入main函数逐行分析。我的常规操作是分三步走先运行一下程序输一个测试flag看看输出格式和报错提示。用binwalk或file命令确认文件类型如果加了壳先脱壳。打开IDA先看导入函数和字符串列表把所有可疑的加密相关调用标出来。这套初筛流程看起来基础但能帮你省去大量盲目分析的精力。如果一篇二进制代码里导入了memcpy、malloc说明有动态内存分配很可能涉及变长数据或动态表如果objdump里看到一个函数被循环调用了多次那八成是某种分组加密的轮函数。5.2 逐层拆解与识别回到这个假设的样本。我搜字符串时没发现标准的Base64表但发现一个可疑的64字符全局数组内容是qwertyuiopasdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM/。这一眼就是自定义Base64表。接着我在反汇编代码里看到一个函数频繁调用了一个查表循环循环内部有 2、 4、 6这种移位操作。再配合刚才找到的64字符数组Base64这层基本确定了。继续往上翻发现Base64编码的结果被传入另一个函数。这个函数内部有个两层循环外层循环256次内层操作了另一个长度为256的数组并且包含swap操作。这里瞬间联想到RC4的KSA。我实际验证一下给这个函数传入一个4字节的key单步跟踪几次发现内外层循环确实是在初始化S盒并搅动它。确认是RC4但需要确定密钥的长度和内容。动态调试断在KSA入口检查传入key的内存布局发现是一个16字节的固定密钥而且这个密钥被硬编码在数据段。再往上看这个key的来源是一个函数生成出来的。进入那个函数我看到了熟悉的0x9E3779B9。这时我基本明白了整个流程很可能是输入flag先经过TEA加密生成key再用这个key去RC4加密最后套一层自定义Base64输出。也就是说三重加密层层嵌套但每层的识别信号都足够明显只要按顺序拆就好。5.3 写脚本验证确认了三层算法后剩下的事情就是写脚本逐层还原。反向顺序先用自定义Base64解码拿到RC4密文再用固定的RC4 key还原出TEA的结果最后解TEA得到真正的输入比较数据。Base64层注意自定义表我直接把标准解码函数的表格参数替换为题目里的这个表剩下逻辑不用动。RC4层确认密钥是16字节、KSA循环256次基本上和一个标准RC4实现一致直接调库验证即可。TEA层要注意delta是否为标准值——调试时我读出sum第一轮的值发现并不是0x9E3779B9而是一个改过的常量。不过没关系程序用这个delta跑完整个加密解密时也换成同一个delta就能还原。真正耗时的地方在于确认TEA的轮数和左右分组的块大小我通过调试器观察到循环总共跑了16轮每轮操作两个32位寄存器确认是简化轮数的标准TEA结构。脚本写完后很快跑出来一个字符串。再用它去和程序内的比较数据对应发现能完全匹配。至此整个题目结束。这个案例想说明的核心方法论是面对多重加密不要试图一次看懂全部而是逐层剥离。先找最外层的特征Base64表再往里推下一层RC4的S盒最后解决最内层TEA的delta和轮数。每一层识别的信号都是独立的识别出一个就立刻固化一个再往下走。6. 常见问题与排查技巧实录平时在群里答疑发现大家做这类题目时遇到的问题其实高度一致。我把高频问题整理成一张速查表同时附上排查思路方便你现场直接对照。问题现象可能的根因排查建议搜到了0x9E3779B9但解密结果不对delta被魔改或者分组顺序做了调整动态调试看第一轮sum值确认delta检查数据是否按64位反序看到了256数组和swap但不确定是不是RC4可能是类RC4的变种或者SP-DEV之类的非标准结构检查swap是否密集出现确认S盒初始化是否S[i]iBase64解码出来是乱码自定义表或编码逻辑被改动搜索64字符可见字符串用已知明文测试编码函数整个程序没有明显的字符串特征出题人做了全加密或字符串动态解密或者用了花指令动态调试下断点dump内存检查是否有解密循环加密流程太多不知道从哪里看起嵌套加密层数多干扰信息过多找输入和比较点逆向追踪数据流逐层剥离密钥长度不确定算法本身不知道密钥固定还是动态生成在KSA入口断点观察传入的key buffer长度我再分享几个比较实用的细节技巧第一RC4密钥长度不确定时直接在KSA的循环里下条件断点观察key[i % key_len]中的key_len是寄存器里的哪个值。动态调试比静态猜要快得多。如果题目故意把key_len藏起来用内存断点在访问key buffer的第一条指令上停住也能看到完整的key长度和内容。第二TEA系列魔改delta时有些出题人会使用运行时计算的值作为delta这时候在静态代码中搜不到标准常量。我的做法是找到sum的累加指令看它对应的立即数这个数就是真delta。如果不方便静态看动态调试跑一轮把sum寄存器直接读取到即可。第三Base64自定义表如果被拆成多个小块分散存储比如字符串不是连续64字符而是在代码里逐字节拼接静态搜索会失效。这时可以考虑在加密函数里对查表位置下内存访问断点程序运行后每次查表命中的地址就是这个表的实际存放位置连续dump即可。还有一个很多人会忽略的坑有些题目会使用改表改填充符的组合魔改。即使找到了自定义表如果填充符不是标准的直接套标准Base64解码函数会因为尾部字节长度不对而失败。所以识别Base64时一定要顺带找到填充符的实际值。一般填充符会出现在编码函数末尾的比较语句中或者在密文的末尾直接可见。另外关于工具链的选择主流的IDA Pro和Ghidra都可以胜任这类分析。Ghidra的优点是免费且能处理多种架构插件生态也够用IDA的优势在于F5反编译对代码结构还原度更高处理复杂的魔改逻辑更顺手。我个人习惯两个组合用Ghidra做初步浏览IDA做深度分析。调试器方面GDB配合pwndbg或gef插件应对绝大多数CTF逆向题绰绰有余。对动态生成的表或运行时计算的内存数据用调试器直接dump往往比在反编译代码里死磕更高效。最后讲一个心态层面的经验识别算法变种这件事本质上是一个先定结构、再找差异的过程。不要因为没直接看到标准常量就慌乱也不要因为看到了标准常量就直接套脚本。算法结构是骨架常量、移位位数、轮数、表内容这些是血肉骨架对了血肉上的差异都是一步一步可以测量出来的。我用这个思路处理过的题目从签到题到决赛题都有目前还没有哪一道是完全看不出算法的——区别只在于你是靠静态特征一眼认出还是靠动态调试多花十分钟确认。下次再看到那些被魔改过的RC4、TEA和Base64先按这个框架走一遍你会发现自己对它们的恐惧纯粹是之前不熟悉造成的。
返回列表