
1. 项目概述从“黑盒”到“白盒”的逆向思维之旅在安全研究、漏洞挖掘乃至日常的调试工作中我们常常会遇到一个核心挑战面对一个编译后的程序、一段混淆的JavaScript代码或者一个看似坚不可摧的加密通信流程我们如何理解其内部逻辑如何验证其安全性又如何复现或绕过其关键机制这就是“算法逆向”所要解决的问题。它远不止是“破解”更是一种深度理解软件行为、验证加密实现、分析协议逻辑的必备技能。本课聚焦于“基础入门”旨在为你搭建一个从密码学算法原理到实际逆向分析的桥梁。我们会深入散列Hash、对称加密如AES、DES、非对称加密如RSA这三大密码学基石并重点探讨在前端安全中极为常见的JavaScript源码逆向场景。无论你是想分析某个网络请求的加密参数审计一段客户端加密逻辑的安全性还是单纯对“程序到底是怎么运行的”感到好奇掌握这些基础都将让你从被动使用工具的“脚本小子”转变为能主动剖析原理的“研究者”。我将结合多年在渗透测试和代码审计中的实战经验带你一步步拆解这些算法在代码中的常见形态并分享如何运用工具和思维让加密的“黑盒”逐渐变得透明。2. 密码学算法核心原理与逆向定位在动手逆向之前我们必须先理解目标是什么。不同的算法在代码中留下的“痕迹”和“模式”截然不同。盲目搜索字符串或跟踪函数调用往往事倍功半。掌握原理才能有的放矢。2.1 散列算法Hash数据的“指纹”散列算法如MD5、SHA1、SHA256等其核心特性是单向性和固定长度输出。你无法从散列值反推原始数据且无论输入多长输出长度固定。在逆向中散列函数通常用于生成数据摘要如文件校验、密码存储需加盐或构造密钥。逆向定位特征常量表Initialization Vectors算法内部使用特定的初始化常量。例如SHA256开头有64个常量字0x428a2f98, 0x71374491, ...。在二进制逆向中在数据段发现大量此类排列规律的常量是识别散列算法的强信号。循环与位操作散列核心是多轮的循环位移、与或非、模加等位运算。在JS逆向中你会看到大量无符号右移、、、|、^、操作。固定输出长度看到一个函数最终输出16字节128位MD5、20字节160位SHA1、32字节256位SHA256或64字节512位SHA512的数据可以优先怀疑是散列。函数名特征在未混淆或轻度混淆的JS代码中可能保留md5、sha1、hash、digest等字样。在编译语言中引用的密码学库函数名也可能暴露如OpenSSL的SHA256_Init、SHA256_Update、SHA256_Final。实操心得不要一看到MD5就认为安全。很多系统错误地直接使用MD5(密码)存储这早已可被彩虹表破解。逆向时关键是找到盐值Salt和迭代次数。搜索concat、字符串拼接或查找一个与用户输入无关的固定字符串那很可能就是盐。2.2 对称加密算法同一把钥匙AES高级加密标准和DES数据加密标准是对称加密的代表。加密和解密使用同一密钥速度快适合大量数据加密。AES已取代DES成为主流。逆向定位特征S盒Substitution-box与逆S盒这是对称加密尤其是AES、DES的标志性组件。它们是预定义的替换表用于实现非线性变换。在二进制中S盒表现为数据段中一大张如AES的256字节看似随机但固定的查找表。在JS代码中可能是一个庞大的数组[0x63, 0x7c, 0x77, 0x7b, ...]。密钥扩展Key Schedule算法会将初始密钥扩展成多轮使用的子密钥。这个过程包含特定的循环和常量。识别密钥扩展例程是定位加密起始点的关键。加/解密模式如ECB、CBC、CFB等。CBC模式会用到初始化向量IV。在逆向网络协议或数据流时如果发现加密数据块前有一个固定长度如AES的16字节的、看似随机且每次传输可能变化的数据块那很可能就是IV。找到IV的生成或传递方式至关重要。固定的块大小AES块固定为16字节DES为8字节。看到代码中有针对数据长度进行padding填充常用PKCS#7或按固定大小分块处理的逻辑是重要线索。注意事项AES有不同密钥长度128 192 256位。逆向时需确定具体是哪一种这通常体现在密钥扩展的轮数上10 12 14轮。此外很多实现会使用OpenSSL或系统库调用AES_encrypt、AES_set_encrypt_key等函数直接搜索这些字符串或导入表能快速定位。2.3 非对称加密算法公钥与私钥的舞蹈RSA是最广为人知的非对称加密算法基于大数分解难题。公钥加密私钥解密私钥签名公钥验签。逆向定位特征大数运算核心是模幂运算m^e mod n。代码中会出现非常大的整数几十上百位的十进制或十六进制数这就是模数n、公钥指数e通常是65537或私钥参数。密钥格式常以PEM、DER格式存储或以-----BEGIN PUBLIC KEY-----这样的Base64编码字符串出现。在JS中可能会直接以{n: “AABBCC...”, e: “010001”}这样的对象形式存在。填充方案原始RSA需要填充如PKCS#1 v1.5或OAEP。逆向时需要关注填充逻辑不正确的填充可能导致攻击。功能场景非对称加密因速度慢多用于加密对称密钥如HTTPS、数字签名。如果你看到程序先用一个RSA加密了一段很短的数据如16-32字节随后再用这个数据去进行大量数据的对称加密那基本就是这种“混合加密”模式。避坑技巧在JS逆向中RSA的实现有时是纯数学库如jsencrypt库有时会调用浏览器的Web Crypto APIwindow.crypto.subtle。后者是浏览器原生实现逆向难度大但可以关注其importKey、encrypt、sign等方法的调用参数和时机。3. JavaScript源码逆向前端加密的攻防战场前端JS加密因其直接暴露在用户侧成为逆向分析中最常见也最具代表性的场景。登录密码、API参数、防爬虫的token其加密逻辑往往藏在混淆的JS代码中。3.1 逆向环境与工具链搭建工欲善其事必先利其器。一个高效的JS逆向环境至少包含以下组件浏览器开发者工具Chrome DevTools / Firefox Developer Tools这是主战场。核心面板Sources查看、调试JS源码设置断点。Network捕获所有网络请求查看请求头、参数、响应体是定位加密入口的起点。Console执行任意JS代码测试函数查看对象。copy()函数可以方便地复制变量值。Application查看和操作本地存储、Cookie。代码美化与格式化线上JS通常被压缩Minify成一行。Sources面板左下角有{}Pretty-print按钮一键格式化让代码恢复可读结构。断点调试技巧XHR/Fetch 断点在Sources面板的XHR Breakpoints中可以添加URL包含特定关键词的断点当发生对应网络请求时自动暂停直接跳到发起请求的JS代码处。事件监听器断点在Sources面板的Event Listener Breakpoints中可以勾选click、submit等事件当用户交互时断住。DOM断点在Elements面板右键节点选择Break on - subtree modifications/attribute modifications/node removal当节点被JS修改时断住。辅助工具Node.js用于在本地独立运行、修改和测试剥离出来的JS加密函数。编辑器和IDE如VSCode用于管理逆向出来的代码片段。Python ExecJS/PyExecJS一种常见的方案将逆向出的JS代码在Python环境中执行便于集成到自动化脚本中。3.2 典型逆向流程以登录密码加密为例假设目标网站登录时密码字段password被加密成一个长长的密文字符串encryptedPassword。我们的目标是还原这个加密过程。步骤一抓包定位加密入口打开浏览器开发者工具F12切换到Network面板勾选Preserve log。在登录框输入测试账号如用户test密码123456点击登录。在Network面板中找到登录请求通常是login、signin或表单提交的请求。查看其Request Payload或Form Data确认password字段已被加密是一串不规则字符。步骤二搜索与断点在开发者工具中按CtrlShiftFWindows打开全局搜索。搜索关键词可以是加密后的密码字符串部分。password、encrypt、encode、key、AES、RSA等。请求的URL端点或参数名。在Sources面板对疑似加密函数的位置打上断点。更高效的方法是使用XHR/Fetch断点在请求的URL中包含login时断住。步骤三调用栈分析与逻辑追踪当断点触发JS执行暂停。此时关注右侧的Call Stack调用栈。调用栈显示了当前暂停的函数是被谁一层层调用的。从调用栈的顶层通常是匿名函数或事件处理函数开始逐层向下点击查看。你的目标是找到明文密码被传入加密函数的那一行以及加密函数本身。在Scope面板中查看当前作用域的变量值特别是包含明文密码的变量。步骤四提取与重构加密函数找到核心加密函数假设叫encryptPassword。在Console中尝试执行encryptPassword.toString()将其源码复制出来。这个函数很可能依赖其他函数或全局变量。你需要顺着依赖关系像剥洋葱一样将encryptPassword函数及其所有依赖其他函数、常量、对象一并提取出来。将提取的代码在Node.js环境中进行测试。创建一个测试文件模拟浏览器环境如提供window、document对象或使用global替代调用加密函数看输出是否与抓包结果一致。关键补环境。JS代码可能依赖浏览器的特定对象或API如navigator、location、crypto等。你需要在Node.js中模拟这些对象使其具备必要的属性和方法这个过程叫“补环境”。实操心得遇到高度混淆的代码变量名变成_0x1a2b3c逻辑被拆散不要试图完全读懂。我们的策略是“黑盒化”确定输入输出将整个混淆的代码块当作一个整体模块导出。在Node.js中只需能正确require这个模块并调用入口函数即可无需理解内部每一行。此外可以尝试使用ast解析等自动化反混淆工具但手动调试分析仍是基本功。3.3 对抗混淆与反调试现代前端保护会采用多种手段增加逆向难度代码混淆变量名混淆、控制流扁平化、僵尸代码插入、字符串加密等。应对策略是使用格式化工具后结合断点调试重点关注网络请求发起前后和用户输入事件处理附近的代码这些是关键逻辑所在混淆程度有时相对较低。反调试debugger语句代码中插入debugger;打开控制台就会不断暂停。解决方法在Sources面板右侧的Breakpoints列表中取消Any debugger statement的勾选或直接找到该行代码禁用断点。检测开发者工具通过判断console对象特征、窗口尺寸差等。可以尝试使用setTimeout重写debugger、使用浏览器插件禁用反调试或在无头浏览器如Puppeteer中运行。无限循环或时间差检测干扰单步调试。可以尝试“条件断点”或“日志点”代替单步直接输出变量值。4. 实战拆解一个混合加密案例的逆向假设我们遇到一个更复杂的场景登录时客户端先用RSA公钥加密一个随机生成的AES密钥再用这个AES密钥加密密码和其他登录数据。逆向分析思路抓包观察发现登录请求有两个关键字段encryptedKey较长像Base64和encryptedData也很长。搜索与断点搜索encryptedKey或publicKey找到RSA公钥通常是一个包含n和e的大对象。在可能生成随机数Math.random、crypto.getRandomValues或调用AES、CryptoJS库的地方下断点。逻辑还原断点触发后通过调用栈和变量观察发现流程如下var aesKey generateRandomKey(16);// 生成16字节随机AES密钥var encryptedKey RSA_encrypt(aesKey, publicKey);// RSA加密AES密钥var loginData {username: ‘...‘ password: ‘...‘};var encryptedData AES_encrypt(JSON.stringify(loginData) aesKey {mode: CBC iv: randomIV});// AES加密数据将encryptedKey和encryptedData发送给服务器。代码提取需要提取generateRandomKey、RSA_encrypt、AES_encrypt三个关键函数及其所有依赖包括公钥、AES的CBC模式和IV生成逻辑。本地模拟在Node.js中用提取的代码复现整个过程。确保本地生成的encryptedKey和encryptedData与抓包数据一致。这里AES的IV通常需要是随机的且需要和密文一起传给服务器有时会拼接在密文前。参数与细节补充AES密钥生成安全的实现应使用window.crypto.getRandomValues(new Uint8Array(16))。如果看到Math.random()其安全性较弱。RSA填充检查RSA_encrypt函数内部很可能使用的是PKCS1Padding。在JS库中可能表现为一个特定的配置对象。IV处理CBC模式的IV必须是随机的且不需要保密。通常它会以明文形式拼接在AES密文之前前16字节或者作为一个单独的字段ivBase64编码发送。在逆向时必须找到IV的生成和传递方式否则无法正确解密encryptedData。5. 常见问题与排查技巧实录在实际逆向过程中你会反复遇到一些典型问题。这里记录一份速查表问题现象可能原因排查思路与解决方案提取的函数在Node中运行报错xxx is not defined依赖了浏览器环境特有的对象如window、document、navigator或Web API。1.补环境在Node脚本开头global.window global;或创建空对象模拟。2.Console中查找在浏览器Console执行console.log(typeof window)等确认缺失对象然后模拟其必要属性。3.使用jsdom库它可以模拟一个完整的浏览器DOM环境但较重。加密结果与网站不一致1. 编码问题字符串与字节数组转换。2. 密钥、IV、盐等参数获取不正确。3. 加密模式或填充方式不对。1.核对编码确保明文、密钥等在传入加密函数前的格式一致。网站可能用UTF-8而你的脚本用了ASCII。使用TextEncoder或Buffer.from(str ‘utf-8‘)明确指定。2.参数比对在浏览器断点处将密钥、IV等变量的值copy()出来与你的脚本中的值逐字比较。3.验证算法用已知的在线加密工具或标准库如Python的cryptography用相同的参数加密一个简单字符串交叉验证你的JS函数结果。断点无法触发或很快跳过1. 代码被动态加载或eval执行。2. 存在反调试机制。3. 加密操作在Web Worker或异步回调中。1.事件监听断点使用submit、click等事件断点。2.脚本黑名单在Sources面板右键脚本选择Blackbox script可以跳过库文件专注于业务代码。3.搜索请求发起搜索fetch、XMLHttpRequest、send等关键字在其附近下断点。4.禁用断点暂时禁用所有断点然后通过“XHR断点”或“DOM断点”等更宏观的方式切入。混淆严重无法理清逻辑控制流扁平化变量名无意义。1.关注输入输出不追求读懂全部找到函数入口和出口将其整体导出。2.动态Hook在Console中重写关键函数如JSON.stringify、Array.prototype.push在其中加入debugger或console.trace()追踪数据流。3.使用反混淆工具如de4js等在线工具或本地npm包尝试自动化反混淆但结果可能需要二次整理。RSA公钥找不到或格式异常1. 公钥被编码如Base64后嵌入JS变量。2. 公钥从网络请求动态获取。3. 使用了非标准的密钥格式。1.搜索大整数在格式化后的代码中搜索非常长的数字字符串十六进制或十进制。2.Network面板搜索在登录前的请求中可能有一个获取公钥的接口。3.解析密钥将找到的Base64字符串用atob()解码或使用Node.js的Buffer和crypto模块尝试解析其PEM或DER格式。独家避坑技巧“最小化复现”原则不要试图一次性提取整个网站的JS。从Network中找到加密入口点只提取与这个加密动作直接相关的函数调用链。像jQuery、React这种大型框架代码完全不用管。善用Console的copy函数在断点暂停时在Console中输入copy(变量名)可以直接将变量的值哪怕是巨大的对象或数组复制到剪贴板粘贴到编辑器中分析比肉眼观察方便无数倍。从结果反推如果加密输出是Base64那么最终步骤一定有btoa或类似的编码。搜索btoa往前追溯其参数往往能快速定位到加密函数的输出点。保持耐心与记录逆向是一个反复试错的过程。每步操作、每个发现的变量值、每个尝试的假设最好都记录下来。清晰的笔记能帮助你在复杂的逻辑迷宫中不至于迷失。掌握算法逆向本质上是培养一种系统性的调试和推理能力。它要求你同时具备密码学的基础知识、编程语言的熟练度以及侦探般的耐心。从识别算法特征到定位关键代码再到提取重构每一步都需要理论与实践紧密结合。当你成功独立完成一次复杂的JS加密逆向并用自己的代码复现出完全一致的结果时那种成就感是无与伦比的。这不仅仅是学会了一项技术更是获得了一把打开无数“黑盒”的万能钥匙。