尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

APP签名校验逆向分析:从抓包到还原wll-kgsa与signature生成逻辑

发布时间:2026/9/29 19:29:50

资讯中心
01
ARTICLE

APP签名校验逆向分析:从抓包到还原wll-kgsa与signature生成逻辑

APP签名校验逆向分析:从抓包到还原wll-kgsa与signature生成逻辑
搞逆向的朋友应该都有过这种经历明明抓包一切正常请求发出去却被服务器一句 invalid signature detected 弹了回来。我上个月在处理一个带某壳加固的安卓应用时就撞上了这堵墙——每个请求体里藏着两个不显眼的参数wll-kgsa 和 signature前者每次请求都变后者看着像哈希但怎么都对不上。这篇文章记录我从抓包到把这两个参数生成逻辑完整还原的过程以及中途踩过的坑。如果你正在接触 APP 请求签名校验或者刚入手加固应用的逆向分析这套思路应该能帮你省下不少时间。先说明一句整个过程基于我拿到授权的测试应用核心目的是安全研究与开发排障。逆向这门手艺技术本身是中性的但拿它做什么就是另一回事了。1. 参数画像先搞清楚对手是什么1.1 从抓包界面的第一印象说起我把目标App的一个交易类接口抓下来典型的POST JSON请求Header部分有固定的设备指纹字段Body里真正可疑的就两个东西signature 是一串40位的十六进制字符串wll-kgsa 则是字母数字混排的定长串。这两个参数的特征非常典型每次请求必变、长度稳定、修改任何一个字节服务端立即拒绝。服务器返回的错误文案五花八门invalid signature detected、register app failed for wechat app signature check failed在微信支付场景里有时还会看到“用户态签名signature错误”。这里的关键不在报错文案而在它们指向同一个事实服务端对请求做了完整性校验。校验不通过任何业务逻辑都不会执行。所以逆向的第一件事不是急着翻代码而是先把参数画像画清楚——谁变谁不变、怎么变、和哪些字段有关系。我当时整理了一个简单表格用于记录参数名长度与格式变化规律疑似用途wll-kgsa定长字母数字混排每次请求都变与时间相关动态token防重放signature40位hex每次请求都变随请求内容变化请求内容完整性签名这个画像直接决定了后面分析方向。wll-kgsa 如果是纯随机那服务端一定还有关联信息能验证最有可能挂在时间戳上signature 如果随内容变化那必然是某种摘要算法参与。两种猜测都需要进代码里验证。1.2 签名参数在接口安全里的常规角色提到 signature做过微信支付接入的人肯定不陌生。微信开放平台的接口签名要求开发者按协议规则拼接参数、用密钥做摘要最后得到签名串一旦拼接顺序或密钥不对服务端就会报签名错误。这次遇到的签名校验本质是同一件事客户端用某个算法生成请求签名服务端用同样的算法验签防止请求在传输过程中被篡改、伪造或重放。常见签名算法按特征很好区分32位hex大概率是MD540位是SHA164位是SHA256或HMAC系列。再看输出是否带随机性——纯摘要是不带随机性的同样的输入必然得到同样的输出如果请求里混入了时间戳和随机token输出自然每次都变。我这次抓到的两个参数正好代表两种不同思路wll-kgsa 偏动态防重放signature 偏内容完整性。分析时一定要分开对待如果一开始就混在一起猜会浪费大量时间。还有个容易被忽略的点签名参数有时候不止一个有的App会把签名拆成两个字段一个负责防重放一个负责内容校验甚至有的会把两者揉进同一个header。你在抓包时不要只看bodyheaders、query、cookies里都可能藏签名参数。这次我就在headers里发现了一个X-App-Token后来才发现它和signature的算法是一套。1.3 为什么“某壳”让难度直接翻倍某壳是常见的安卓加固方案会把原始dex加密隐藏应用运行时才在内存中动态解密。直接拿APK解包看到的classes.dex只是壳的加载器真正的业务逻辑根本不在磁盘上。这意味着你全局搜索 wll-kgsa 这个字符串在静态包里大概率搜不到——不是参数不存在而是承载它的dex还没被释放出来。所以整理环境的阶段第一要务就是想办法把运行时的真实dex拿在手里。另外加固方案的版本会影响脱壳难度。老的壳可能一个Frida脚本就搞定了新版往往带反调试、反Frida、反内存dump。动手之前先花十几分钟确认壳的版本和特征能省掉后面一大段折腾。我这次碰到的版本不算新但已经带了一些基础的检测需要先绕过几个点才能正常hook。2. 从环境准备到第一行有效代码2.1 抓包与对抗检测的基础配置我用的设备是刷了Android 9的Pixel模拟器装好Charles并给系统安装用户证书打开SSL解密就能看到HTTPS明文。但某壳这类加固方案往往做防抓包检测比如校验SSL证书指纹、检测HTTP代理。遇上这种情况最直接的办法是绕过证书校验逻辑我习惯在Frida里挂一个SSL pinning bypass脚本让抓包工具顺利看到请求和响应的明文。这里有几个小经验Frida的server版本和电脑端frida-tools版本必须严格对应版本不一致经常会出现attach成功但执行脚本毫无反应的情况。加固App通常有反调试检测如果一启动就被杀掉建议先用最简单的探测脚本确认是在哪个模块卡住再针对性绕过。模拟器容易被检测如果目标App在模拟器里直接闪退可以改用真机很多加固方案对模拟器有硬性检测。抓包确认请求正常发出后随手多抓十几个样本。样本越多后面分析签名算法时做对比验证就越轻松。这个习惯帮我省了不少事强烈建议保留。2.2 脱壳的常规操作与注意点针对某壳最实用的脱壳方式是内存dump壳在运行时会解密原始dex我们在内存里直接把它捞出来。大致思路是遍历当前ClassLoader的pathList把每个dex文件的字节读取出来保存到本地// Frida脚本的思路示意细节按实际环境调整 Java.perform(function () { // 获取当前ClassLoader的pathList字段 // 遍历pathList中的dexElements // 对每个dex读取其字节内容写入 /data/data/package/files/ });dump出来的dex拖回电脑用jadx批量打开。如果dex是分片或多dex结构就按文件编号逐个看。脱壳这块实在有太多可讲的反调试、反Frida、VMP的对抗都能单开一篇这里不展开只说结论只要真实dex能dump出来后面静态分析就有个大半胜算了。提示脱壳得到的dex文件不要急着删除后续反复查看时非常有用。我习惯按日期归档到独立目录并记录当时的脱壳方式和壳版本方便回查。2.3 第一次在jadx里搜到参数名拿到真实dex之后最直接的动作是在jadx里全局搜索 wll-kgsa。这一步通常会带来突破性信息。我这次的发现是wll-kgsa 和 signature 被同时放入一个TreeMap然后一起参与后续的排序拼接。代码大概长这样// 混淆后的伪代码仅示意逻辑 map.put(wll-kgsa, nativeGetKgSA(timestamp)); map.put(signature, signer.sign(joinedParams, appKey));看到这样的代码两条线索瞬间清晰wll-kgsa 来自一个native方法signature 来自一个静态签名器。前者指向so库后者指向同一套底层加密逻辑。接下来就是顺着调用关系往上翻看看这些方法在哪个类里被调用、调用前又拼接了什么字段、哪些字段是动态的、哪个是固定的。在jadx里右键点击引用时不要只看赋值语句要看它周围几十行的上下文——构造TreeMap之前有哪些字段被写进来了、requestId是不是也参与了、host和path有没有被拼进去。这些上下文直接决定了后面的签名拼接串长什么样。我在分析中发现除了常见的时间戳和设备ID客户端还把当前页面路径、App版本号、随机生成的nonce一起塞进了待签名数据。这类“多余字段”恰恰是签名校验最容易出错的地方少了任何一个字段服务端验签都会失败。3. 顺藤摸瓜从字符串到so库的追踪3.1 回溯调用链把上下文看全分析到这里其实已经过了最难的关口。顺着引用关系我找到了一个网络请求封装类里面 wll-kgsa 和 signature 的赋值点紧挨在一起。这不是巧合——它们应该服务于同一个目的在请求发出前对参数集合做签名。这里有个经验很多新手一看到 native 方法就急着去打开IDA其实可以先在Java层看清楚调用链。有时候签名的核心逻辑不一定只在so里Java层可能已经做了大部分工作so里只是一个摘要函数而已。我在追调用链时发现签名前的字符串拼接、参数过滤、排序都在Java层完成native只负责最后一步的HMAC计算。如果一开始就钻到so里反而容易错过字段级的细节。3.2 追到JNI算法为什么藏在so里继续往深处点发现 nativeGetKgSA 是一个native方法说明核心算法在so文件中。打开IDA加载对应的arm64-v8a so库在导出表里找到 Java_包名_类名_nativeGetKgSA 之类的函数。F5反编译之后里面无非是位运算、循环和查表——典型的哈希或对称加密结构。这里想提醒一点so库可能不止一个别只盯着一个看。有些App会把核心签名逻辑拆到独立的so里再加一层壳保护有些则把签名逻辑和一些已知的开源哈希库混编代码特征明显但数量庞大。判断关键是先用字符串和导出函数做初步筛选找到真正包含目标符号的那个so。3.3 快速判断算法的两个实用技巧拿到反编译代码后怎么快速判断它是什么算法我常用的方法有两个。第一看特征常量。MD5有四个固定初始常量0x67452301等SHA1有五个SHA256有八个。IDA的Hex视图中直接搜这些值命中哪个基本就是哪个算法准确率非常高。我这次在so里搜到SHA256的常量后心里就有底了剩下的工作就是确认它外面套没套HMAC那层。第二看样本输出。抓几个不同的请求入参和输出先在本地用开源库按猜想的算法重算一遍如果结果一致算法类型基本锁定。这个方法配合Frida hook native函数能拿到方法级的输入输出比纯静态分析靠谱得多。我实际操作时用Frida hook了native方法的参数和返回值把一组真实的输入输出记录下来接着在本地用HMAC-SHA256重算第一次就撞对了那种兴奋感估计每个逆向人都体会过。4. wll-kgsa与signature的生成逻辑还原4.1 wll-kgsa实际上是一个动态token通过上面的定位我在native层里确认了wll-kgsa的生成依赖三样东西当前时间戳、会话初始化的随机种子、设备唯一标识的哈希结果。它本质上是一个动态token作用是防止请求重放——每次请求都变服务端对照时间窗口内出现的值来校验是否新鲜。构造流程大致可以概括成取当前毫秒级时间戳与随机种子做一次位轮转运算拼上设备指纹的哈希摘要最终得到定长的 wll-kgsa。这类设计在很多App里都有只是名字不同有的叫nonce、有的叫tk。一旦你掌握了它的生成逻辑本地复现请求时再生成一个同等格式的值就行不需要关心它是真是假只要服务端能验过即可。这里要注意的是动态token看起来像随机数但往往不是纯随机而是时间和种子的函数。如果你单独看一个请求很可能以为它是随机生成抓多个请求对比时间戳和输出值的差异才能发现它和时间的强关联。我在对比几个样本之后发现wll-kgsa会随时间戳递增出现规律性变化这个观察帮助我确认了生成逻辑。4.2 signature的算法还原与拼装顺序signature走的是经典HMAC路线。结合so库反编译结果和jadx里的拼接代码最终还原出的逻辑是1. 取请求体中除signature和wll-kgsa外的所有业务字段 2. 按照key的字典序升序排列 3. 拼接成 k1v1k2v2 格式 4. 在末尾追加wll-kgsa的值和当前时间戳 5. 使用固定appKey做HMAC-SHA256 6. 将结果转为大写hex作为signature这里要特别说明具体appKey我不贴出来。实际提取appKey的过程需要从so里解混淆、异或还原一步都不能错。这种密钥一般不是明文存在so里的可能做过多层变换先异或一个固定字节串再Base64解码甚至可能隐藏在资源文件里运行时读取再计算。提取过程需要耐心跟读反编译代码。4.3 为什么拼装顺序最让人抓狂签名校验最经典的坑就是字段顺序微信支付的签名错误绝大多数也是栽在拼接顺序上。例如把signature字段自己也放进了待签名列表、把sign字段用原始名而不是value、或者多一个空格少一个都会导致签名对不上。我在还原过程中用控制变量的方式对比了几组样本先固定所有字段值只调整拼接顺序再逐一重算签名和线上值做比较。大概试了几十种组合才找准正确的格式。后来我把这个过程脚本化了几秒钟能跑几百种组合效率一下子提上来。# 思路示意用脚本批量尝试不同拼接顺序 import itertools def try_combinations(params, app_key, target): keys list(params.keys()) for perm in itertools.permutations(keys): raw .join(f{k}{params[k]} for k in perm) # 这里省略精确的拼接规则仅展示思路 sig hmac.new(app_key.encode(), raw.encode(), hashlib.sha256).hexdigest().upper() if sig target: return perm return None这个脚本只是展示思路现场跑的时候还要把各种编码差异、追加字段、大小写转换都考虑进去。千万不要一开始就穷举全部排列先把字段名称和固定值确认了缩小搜索空间再跑效率完全不一样。5. 从还原到复现验证流程与踩坑实录5.1 本地复现的正确姿势算法和密钥都拿到后最激动人心的就是本地复现。我写了一个Python模拟客户端用完全相同的拼接逻辑和密钥对抓到的历史请求做重算发现线上signature和本地计算结果完全一致。那一刻基本可以确认逆向分析没有白费。不过在本地复现阶段有几点要特别留意。第一不要在正式环境大量刷请求你的目标只是验证算法正确性不是压测。第二尽量用一个隔离的测试账号和临时环境避免对线上系统造成影响。第三复现脚本里的密钥和业务参数要严格保密不要往公开仓库里传。这些都可能造成不必要的风险。5.2 我踩过的三个典型坑这个项目里我踩过的坑不少挑三个最有代表性的写一下。第一个坑是时间戳偏差。用本地时间生成的时间戳比服务器时间快了不到一分钟签名算法再正确服务端也拒绝。排查半天才想到去看客户端和服务器的时间差后来在脚本里加了对时逻辑问题就解决了。这道坎几乎所有人都会遇到越早注意到越好。第二个坑是URL编码差异。某些字段的值里含空格和特殊符号在拼接到待签名串时客户端用了URLEncoder.encode()而我在脚本里直接用原始字符串导致签名一直不匹配。后来把编码这一步加上才算通过。这类编码差异在遇到特殊字符时特别容易踩尤其是用户输入相关的字段。第三个坑是密钥提取不完整。so里的appKey并不是一行明文而是先异或了一个固定字节串、再做了Base64解码后才得到真正密钥。我一开始只提取了前半截后面发现密钥长度明显不对重新跟了一遍解密流程才拿到完整值。这类多层变换是常见的隐藏手段提取时一定要跟踪完整的计算路径。5.3 跟“invalid signature detected”等报错的排查对照很多朋友在网上搜“invalid signature detected怎么解决”或者在接微信支付时遇到“register app failed for wechat app signature check failed”、“用户态签名signature错误”本质上都是同一类问题服务端验签失败。排查时可以按这个清单逐项核对。报错表现常见原因排查方向每次请求都报签名错误算法、密钥或拼接顺序不对对照协议核对签名生成流程偶尔报错、和发起时间相关时间戳偏差超出窗口校准客户端与服务器时间只在特殊字符时出错URL编码与协议不一致对比编码方式URLEncoder/UTF-8原文换机型后开始报错设备指纹参与签名取的字段不一致核对设备唯一标识的获取方式签名算法版本升级后报错新旧版本算法不一致确认服务端和客户端版本匹配我自己在排这类问题时最推荐的路径始终是先抓一个确定成功的请求样本再拿脚本逐字段重算。只要有一个成功样本在手剩下的都是做排除法。怕就怕手里没有样本闭着眼睛改签名那叫猜不叫排查。6. 关于逆向这件事说点大实话6.1 合法授权是最大的前提没有之一这篇文章写出来不是教你去破解别人的支付签名而是记录一套通用的分析思路。能在什么场景用你维护的App出了签名报错但找不到原因、你负责的SDK需要排查安全隐患、你接手了一个无文档的遗留系统需要了解它的请求协议。这些场景都有一个共同点系统是你自己或授权方的。没有得到授权就去逆向别家应用并尝试篡改请求这个行为本身就踩线了。6.2 逆向分析最大的受益者其实是开发者自己把签名算法看懂之后最大的收获其实是能反过来指导开发。比如排查自己App的签名错误时有了这套方法论定位速度可以提高一个量级设计新协议时也能有意识地把随机防重放、内容完整性校验、密钥动态混淆这些手段加进去。你越是知道攻击者会怎么逆向你越能把防线配置得更合理。微信支付签名错误这类开发问题回顾起来也多半是拼接顺序、字段遗漏、时间同步这些基础点并不需要太多玄学。6.3 一点个人体会这次只讲了从抓包到算法还原的链路脱壳脚本的具体实现、反调试绕过、so层面的动态Hook这些内容都还没有展开。后面我再按主题补齐。我自己在写这篇复盘时最大的体会是逆向真没什么天才操作无非是样本抓得足够多、调用链跟得足够细、验证脚本写得足够快量变到质变而已。希望这个案例能给你一点可复用的思路。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。