某车联网App 通讯协议加密分析(四) Trace Code
一、目标
之前我们已经通过Trace Block 来比对了Unidbg和App跑的结果。现在他们运行的流程都差不多了,但是结果还是不对,今天我们就要通过Trace Code进行更细致的对比。
v6.1.0
二、步骤
缩小Trace的范围
Trace Code那么好使,我们为什么不一上来就Trace一遍?
因为Trace Code的粒度太细了,一上来就搞,跑出几百万行结果,根本没法看。
我们通过Trace Block已经在逐步缩小范围了。
JNIEnv->GetStringUtfChars("FlK6XicivmCwPSE3sk6b71m9WbWd/gYZtlajqGXhEXXjmWEZziR51rVWSEDwUUi4UN9RnoCGbLNmqI80Fiog4Sw==") was called from RX@0x4002b744[libencrypt.so]0x2b744sub_2b8b8sub_2b800sub_2b800sub_2b800sub_2b800sub_2b828sub_ab6csub_a528sub_ab9csub_a848sub_a7e8sub_a7c4sub_8ee4sub_7334sub_8f40sub_95e0........sub_a7c4sub_8ee4sub_7334sub_8f40sub_96bcsub_9a2csub_9268sub_a818sub_a90csub_a928sub_2b854
JNIEnv->ReleaseStringUTFChars("FlK6XicivmCwPSE3sk6b71m9WbWd/gYZtlajqGXhEXXjmWEZziR51rVWSEDwUUi4UN9RnoCGbLNmqI80Fiog4Sw==") was called from RX@0x4002b86c[libencrypt.so]0x2b86c
我们的目标大概率是在 0x7000 → 0xa000 这个地址范围之类。
定位Trace Code目标
翻一翻IDA里面的导出表
1:ida
比较合眼缘的就这三个了,0x7184 ,0x77A4 貌似都没有被Trace Block命中,感觉幕后大boss应该就是这个 0x8EE4
Tip:
可以在怀疑的几个函数上加个hook,看看是否命中。
Unidbg Trace Code
emulator.attach().addBreakPoint(module.base + 0x8EE4 , new BreakPointCallback() {@Overridepublic boolean onHit(Emulator<?> emulator, long address) {System.out.println(" ====== traceCode ====== ");UnidbgPointer pX = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1);byte[] bData = pX.getByteArray(0,16);String strLabel = String.format("x0值 0x%08x", pX.peer);Inspector.inspect(bData,strLabel);try {emulator.traceCode(module.base + 0x8EE4, module.base + 0x9C0C).setRedirect(new PrintStream(new File("traceCodeCar.log")));} catch (IOException e) {throw new IllegalStateException(e);}return true;});}
先在 0x8EE4 加个断点,然后开始Trace Code。 参数是Trace的起始地址和结束地址,已经保存的结果文件。
从这个Trace结果来看,每16个字节去做解密。典型的AES。
Frida Trace Code
Stalker的好处很多,最大的功能是可以过反调试,所以基于Stalker去写Trace Code,写了好几个版本,一直不大满意。
前几天发现了
https://github.com/IIIImmmyyy/frida-trace
比较帅,拿来改了改。效果很好。(只支持Arm64)
let moduleBase;
let isFirstIn = true;
let pre_regs;
let infoMap = new Map();
let detailInsMap = new Map();function parserNextAddr(ins) {let s = JSON.stringify(ins);let address = ins.address;// log("address:"+address)let offset = address - moduleBase;let s1 = (offset).toString(16);let entity = {};entity.address = offset;return s1;
}const byteToHex = [];
for (let n = 0; n <= 0xff; ++n) {const hexOctet = n.toString(16).padStart(2, "0");byteToHex.push(hexOctet);
}function hex(arrayBuffer) {const buff = new Uint8Array(arrayBuffer);const hexOctets = [];for (let i = 0; i < buff.length; ++i)hexOctets.push(byteToHex[buff[i]]);return hexOctets.join("");
}function formatArm64Regs(context) {let regs = [];regs.push(context.x0);regs.push(context.x1);regs.push(context.x2);regs.push(context.x3);regs.push(context.x4);regs.push(context.x5);regs.push(context.x6);regs.push(context.x7);regs.push(context.x8);regs.push(context.x9);regs.push(context.x10);regs.push(context.x11);regs.push(context.x12);regs.push(context.x13);regs.push(context.x14);regs.push(context.x15);regs.push(context.x16);regs.push(context.x17);regs.push(context.x18);regs.push(context.x19);regs.push(context.x20);regs.push(context.x21);regs.push(context.x22);regs.push(context.x23);regs.push(context.x24);regs.push(context.x25);regs.push(context.x26);regs.push(context.x27);regs.push(context.x28);regs.push(context.fp);regs.push(context.lr);regs.push(context.sp);regs.push(context.pc);return regs;
}
function getPcReg(regs) {return regs[32];
}
function isRegsChange(context, ins) {let currentRegs = formatArm64Regs(context);let logInfo = "";for (let i = 0; i < 32; i++) {if (i === 30) {continue;}let preReg = pre_regs[I];let currentReg = currentRegs[I];if (Number(preReg) !== Number(currentReg)) {if (logInfo === "") {//尝试读取stringlet changeString = "";try {let nativePointer = new NativePointer(currentReg);changeString = nativePointer.readCString();}catch (e) {changeString = "";}if (changeString !== "") {currentReg = currentReg + " (" + changeString + ")";}logInfo = "\t " + getRegsString(i) + " = " + preReg + " --> " + currentReg;}else {logInfo = logInfo + "\t " + getRegsString(i) + " = " + preReg + " --> " + currentReg;}}}//打印PC寄存器let parse = JSON.parse(ins);let mnemonic = parse.mnemonic; //补充strif (mnemonic === "str") {let strParams = getStrParams(parse, currentRegs);logInfo = logInfo + strParams;}else if (mnemonic === "cmp") {let cmpParams = getCmpParams(parse, currentRegs);logInfo = logInfo + cmpParams;}else if (mnemonic === "b.gt" || mnemonic === "b.le" || mnemonic === "b.eq" || mnemonic === "b.ne" || mnemonic === "b") {// log(ins)let bgtAddr = getbgtAddr(parse, currentRegs);logInfo = logInfo + bgtAddr;}let entity = {};entity.info = logInfo;let address = parse.address;if (lastAddr === undefined) {entity.color = getColor();lastAddr = address;}else {let number = address - lastAddr;if (number === 0x4) {entity.color = getColor();}else {currentIndex++;entity.color = getColor();}lastAddr = address;}pre_regs = currentRegs;return entity;
}
let lastAddr = undefined;
let currentIndex = 0;
function getColor() {return "";if (currentIndex > 1) {currentIndex = 0;}if (currentIndex === 0) {return "C35"; // logger_1.LogColor.C35;}else if (currentIndex === 1) {return "C97"; // logger_1.LogColor.C97;}else if (currentIndex === 2) {return "C97"; // logger_1.LogColor.C97;}
}
function getRegsString(index) {let reg;if (index === 31) {reg = "sp";}else {reg = "x" + index;}return reg;
}
function getbgtAddr(parser, currentRegs) {let bgtAddr = "";let operands = parser.operands;for (let i = 0; i < operands.length; i++) {let operand = operands[I];if (operand.type === "imm") {let value = operand.value;let number = value - moduleBase;bgtAddr = "\t block addr:" + number.toString(16);break;}}return bgtAddr;
}
function getStrParams(parser, currentRegs) {let operands = parser.operands;for (let i = 0; i < operands.length; i++) {let operand = operands[I];if (operand.type === "reg") {//获取valuelet value = operand.value;if (value === "wzr") {return "\t " + "str = 0";}else {let replace = value.replace("w", "");let index = replace.replace("x", "");let index_reg = currentRegs[index];let changeString = "";try {let nativePointer = new NativePointer(index_reg);changeString = nativePointer.readCString();}catch (e) {changeString = "";}//读取值if (changeString !== "") {index_reg = index_reg + " (" + changeString + ")";}return "\t " + "str = " + index_reg;}}}
}
function getCmpParams(parser, currentRegs) {let operands = parser.operands;let cmpInfo = "";for (let i = 0; i < operands.length; i++) {let operand = operands[I];if (operand.type === "reg") {let value = operand.value;let replace = value.replace("w", "");let index = replace.replace("x", "");let index_reg = currentRegs[index];let changeString = "";try {let nativePointer = new NativePointer(index_reg);changeString = nativePointer.readCString();}catch (e) {changeString = "";}//读取值if (changeString !== "") {index_reg = index_reg + " (" + changeString + ")";}cmpInfo = cmpInfo + "\t " + value + " = " + index_reg;}}return cmpInfo;
}function ZY_unTraceAddrEnd(){var tid = Process.getCurrentThreadId();Stalker.unfollow(tid);Stalker.garbageCollect();console.log(TAG + " ======== unTraceAddr_End");
}function ZY_trace_Stalker_begin(soname, addr, size) {let module = Process.findModuleByName(soname);moduleBase = module.base;console.log(TAG + JSON.stringify(module));console.log(TAG + "addr = " + addr);console.log(TAG + "size = " + size);Interceptor.attach(moduleBase.add(addr), {onEnter: function (args) {this.pid = Process.getCurrentThreadId();// console.log(TAG + " ==== ZY_trace_Stalker_begin ==== ");//看下结构体的值Stalker.follow(this.pid, {events:{call:false,ret:false,exec:false,block:false,compile:false},onReceive:function(events){},transform: function (iterator) {let lastInfo;const instruction = iterator.next();let startAddress = instruction.address;// console.log(TAG + "startAddress:" + startAddress + " base:" + module.base );if (size === 0) {size = module.size;addr = 0;}const isModuleCode = startAddress.compare(moduleBase.add(addr)) >= 0 &&startAddress.compare(moduleBase.add(addr).add(size)) < 0;do {if (isModuleCode) {// console.log(TAG + instruction.address + ":" + instruction);let s = parserNextAddr(instruction);let address = instruction.address;let offset = address - moduleBase;let lastInfo = s.toString(16) + "\t\t" + instruction;detailInsMap.set(offset, JSON.stringify(instruction));infoMap.set(offset, lastInfo);iterator.putCallout(function (context) {let regs = JSON.stringify(context);if (isFirstIn) {isFirstIn = false;//保存寄存器pre_regs = formatArm64Regs(context);}else {//打印的实际是上一次的 这样延迟一次可以打印出寄存器变化let pcReg = getPcReg(pre_regs);let offset = Number(pcReg) - moduleBase;let logInfo = infoMap.get(offset);let detailIns = detailInsMap.get(offset);// log("detailIns:"+detailIns)let entity = isRegsChange(context, detailIns);console.log(TAG + logInfo + " ; " + entity.info, entity.color);}});}iterator.keep();} while (iterator.next() != null);},});},onLeave: function (ret) {// libtprt.saveStringMapTofile();Stalker.unfollow(this.pid);console.log(TAG + "ret:" + ret);}});
}
调用方法
ZY_trace_Stalker_begin('libencrypt.so',0x8EF4, 0x9C0C - 0x8EF4);
Tip:
Trace之前可以先匹配入参,只Trace指定的密文。然后unidbg 去 Trace 同样的密文,这样有利于比对。
对比结果
前戏铺垫的太长了,总算拿到Trace Code的结果了。
1:main
这里有个很奇怪的地方,
9608: "ldr w8, [x9, x8, lsl #2]" x9=0x40147510 x8=0x9094 => w8=0xf54de125
0x40147510 这个地址是在so的数据段。 这段代码的意思是 从 0x40147510+(0x9094<<2) = 0x4016B760 这个地址取数据
m0x4016B760>-----------------------------------------------------------------------------<
[11:57:20 551]RW@0x4016b760[libencrypt.so]0x16b760, md5=cc2b1f1f88429f40f84599a613cf3143, hex=25e14df5a6ea8219a148998cebd3d9b5e55522c3109f9bd0347f27854da2729b83f3a390ce0c318645a588f14b339626692918f7119819acb36b0f92cd0dc4d596a74b1bc8f1263f2841c72757b265b4b1692c56a24a9c8f528a41e0d1d95149e25a0120a9e884147d223f7323eb4ff2
size: 112
0000: 25 E1 4D F5 A6 EA 82 19 A1 48 99 8C EB D3 D9 B5 %.M......H......
0010: E5 55 22 C3 10 9F 9B D0 34 7F 27 85 4D A2 72 9B .U".....4.'.M.r.
从unidbg里面打印一下,没毛病。 就是 25 E1 4D F5
但是对应的App的结果就有点意思了
9608 ldr w8, [x9, x8, lsl #2] ; x8 = 0x90f7 --> 0x73b55372
0x40147510+(0x90f7<<2) = 0x4016B8EC
m0x4016B8EC>-----------------------------------------------------------------------------<
[12:00:52 810]RW@0x4016b8ec[libencrypt.so]0x16b8ec, md5=cd71edc06f5f49b56a700c1f6e541610, hex=e35324c7bb9b61e3b06ee0f4f571fb062a44630bc501d369b49e3e2cee56eaeb463ab0c57e5c3b12a8abf8f81264047dd98a871bc1b84e9d807db83241a8ee1d0be8aeecd8e1328a7311b4a73e2c9b925f24946cc35eacb6953472a0b4cdc9baebce5c65701ab7afa0f48251f1e5afab
size: 112
0000: E3 53 24 C7 BB 9B 61 E3 B0 6E E0 F4 F5 71 FB 06 .S$...a..n...q..
0010: 2A 44 63 0B C5 01 D3 69 B4 9E 3E 2C EE 56 EA EB *Dc....i..>,.V..
这个地址对应的结果不对。 难道App会在内存中变异?
m0x4017B8EC>-----------------------------------------------------------------------------<
[12:01:51 113]RW@0x4017b8ec[libencrypt.so]0x17b8ec, md5=09b5524828f471a42a3e75beed7b23a8, hex=7253b573f596a2d945a40787a842b3a0e46eadcc761474b12000e2a1ec0ab7053cca47f537ed81c4b823dc9866cc99a7b5b4b4adb3fffa867aa45080ea698ddb04e771872ee075f999b9ba241908bc5ac29666eb5f6a7800abee8213d52a28f19321df19b077a1716dbf3ae72a10f33f
size: 112
0000: 72 53 B5 73 F5 96 A2 D9 45 A4 07 87 A8 42 B3 A0 rS.s....E....B..
0010: E4 6E AD CC 76 14 74 B1 20 00 E2 A1 EC 0A B7 05 .n..v.t. .......
在unidbg的内存里面搜索了一下,发现加上 0x10000 之后的值恰好是App里显示的一致。
真相只有一个
pc时代过来的老同学就很敏感了。这个so并不是原始so,而是我们在内存中dump出来的。
我们观察下原始so的节表
fenfei$ greadelf -l libencrypt.soElf file type is DYN (Shared object file)
Entry point 0x3210
There are 5 program headers, starting at offset 64Program Headers:Type Offset VirtAddr PhysAddrFileSiz MemSiz Flags AlignLOAD 0x0000000000000000 0x0000000000000000 0x00000000000000000x000000000002f288 0x000000000002f288 R E 0x10000LOAD 0x000000000002fba0 0x000000000003fba0 0x000000000003fba00x0000000000194a08 0x0000000000194ab8 RW 0x10000DYNAMIC 0x00000000001c5b90 0x000000000003fc00 0x000000000003fc000x0000000000000210 0x0000000000000210 RW 0x8
readelf: Error: no .dynamic section in the dynamic segmentGNU_EH_FRAME 0x000000000002cd10 0x000000000002cd10 0x000000000002cd100x000000000000048c 0x000000000000048c R 0x4LOAD 0x00000000001c5078 0x00000000001d5078 0x00000000001d50780x0000000000000afc 0x0000000000000afc R E 0x1000
文件地址 0x2fba0 映射到了 内存地址 0x3fba0
所以我们dump出来的so要修复这个映射
Program Headers:Type Offset VirtAddr PhysAddrFileSiz MemSiz Flags AlignLOAD 0x0000000000000000 0x0000000000000000 0x00000000000000000x000000000002f288 0x000000000002f288 R E 0x10000LOAD 0x000000000003fba0 0x000000000003fba0 0x000000000003fba00x0000000000194a08 0x0000000000194ab8 RW 0x10000DYNAMIC 0x00000000001c5b90 0x000000000003fc00 0x000000000003fc000x0000000000000210 0x0000000000000210 RW 0x8
readelf: Error: no .dynamic section in the dynamic segmentGNU_EH_FRAME 0x000000000002cd10 0x000000000002cd10 0x000000000002cd100x000000000000048c 0x000000000000048c R 0x4LOAD 0x00000000001c5078 0x00000000001d5078 0x00000000001d50780x0000000000000afc 0x0000000000000afc R E 0x1000
把我们dump出的so头里面的 0x2fba0 改成 0x3fba0
这次终于可以成功解密了
call decheckcode: {"code":"0","message":"success","respondData":{"serverTime":1663643024410,"timeSpan":"5","loginFlag":0}}
三、总结
忙活了老半天,其实最后只做了一下修复so文件头。严格意义上只改了3个字节。
改3个字节很简单,分析并知道如何改,再哪改,才是我们的重点。
Trace Function 、Trace Block 、Trace Code。逐渐缩小范围来定位。
还可以通过不同的入参来TraceCode,对比一下更有助于分析算法。
1:ffshow
纵浪大化中 不喜亦不惧 应尽便须尽 无复独多虑
某车联网App 通讯协议加密分析(四) Trace Code相关推荐
- 某app登录协议逆向分析
某app登录协议逆向分析 设备 iphone 5s Mac Os app:神奇的字符串57qm5Y2V 本文主要通过frida-trace.fridaHook.lldb动态调试完成破解相应的登录算法, ...
- 关于SQL Server通讯协议加密及与JDBC通讯问题处理
最近项目部署遇到第三方安全检测问题,记录一下 SQL Server通讯协议加密问题处理 首先将数据库升级到最高版本,按照配置方法处理SQL Server设置强制加密后,找不到签名证书(不知如何设置), ...
- 逆向分析:还原 App protobuf 协议加密
前言 之前有记录js逆向.安卓逆向等,今天这里记录下一些协议逆向,这种一般出现在websocket 协议. protobuf 协议等,某音,B站 APP等都有用到这些协议加密,而我们不再是像 js 端 ...
- android中的sign加密,[原创]某交通app的sign加密分析
Readmeapp名称: 6L2m5p2l5LqG 版本号: My45MS4w 环境及工具:macos objection frida 加密分析 该app没有抓包限制, 直接charles就可以完整抓 ...
- 某电商App 返回数据加密解密分析(四)
一.目标 最近在抓包某电商App的时候发现一个加密数据,它在做通讯地址请求的时候,请求数据做了加密.返回数据中的地址信息也是密文. 今天我们的目标就是这个数据的加密解密. App版本: v10.3.0 ...
- IM通讯协议专题学习(八):金蝶随手记团队的Protobuf应用实践(原理篇)
本文由金蝶随手记技术团队丁同舟分享. 1.引言 跟移动端IM中追求数据传输效率.网络流量消耗等需求一样,随手记客户端与服务端交互的过程中,对部分数据的传输大小和效率也有较高的要求,普通的数据格式如 J ...
- 某音App protobuf协议还原逆向分析
趣味模块 小红是一名爬虫开发工程师,自从上次小红解决了字体反爬.websocket协议.B站protobuf协议后,小红一直所向披靡,过五关斩六将,在一个多月的时间里一直没有遇到过有难度的问题.但是今 ...
- 端对端加密通讯协议Signal protocol 学习(转)
转载:https://www.jianshu.com/p/e1f6f01c65f8 前段时间学习了对称加密/非对称加密算法,了解了不同类型加密算法的应用场景.最近一直在关注Mixin项目,对其采用的加 ...
- Skype通讯协议分析
晚上在看Salman A. Baset和Henning Schulzrinne写的<An Analysis of the Skype Peer-to-Peer Internet Telephon ...
最新文章
- docker 修改镜像地址
- 拼图游戏_我最喜欢的Java拼图2 + 1 = 4
- html 页间传送数据,js 不同页面间传递值并取值,html不同页面间数据传递
- 解决orcale报ORA-28001: the password has expired
- DM365 color space
- javafx动画_JavaFX动画工具
- Spring Cloud各组件总结归纳
- 动态规划--Leetcode63.不同路径二
- 案例分享|某医药集团的BI建设案例
- 参数模型 非参数模型 生成模型 判别模型
- 佳能MP258mp259清零软件
- 如何安装PyAudio
- python之操作mysql数据库
- 百度音乐助手 下载高品质音乐
- 投资信条:你不可不知的投资理念
- 强者越强-效率与公平的幂律视角
- [GNSS] GNSS原理:多模导航卫星精密定轨理论
- (Java实现) 美元汇率
- 数据结构与算法学习(第九天)(系列结束)
- python 股票交易接口 github_GitHub - gusihao/vnpy: 基于python的开源交易平台开发框架...