某车联网App 通讯协议加密分析(四) Trace Code

一、目标

之前我们已经通过Trace Block 来比对了Unidbg和App跑的结果。现在他们运行的流程都差不多了,但是结果还是不对,今天我们就要通过Trace Code进行更细致的对比。

v6.1.0

二、步骤

缩小Trace的范围

Trace Code那么好使,我们为什么不一上来就Trace一遍?

因为Trace Code的粒度太细了,一上来就搞,跑出几百万行结果,根本没法看。

我们通过Trace Block已经在逐步缩小范围了。

1JNIEnv->GetStringUtfChars("FlK6XicivmCwPSE3sk6b71m9WbWd/gYZtlajqGXhEXXjmWEZziR51rVWSEDwUUi4UN9RnoCGbLNmqI80Fiog4Sw==") was called from RX@0x4002b744[libencrypt.so]0x2b744 2 sub_2b8b8 3 sub_2b800 4 sub_2b800 5 sub_2b800 6 sub_2b800 7 sub_2b828 8 sub_ab6c 9 sub_a528 10 sub_ab9c 11 sub_a848 12 sub_a7e8 13 sub_a7c4 14 sub_8ee4 15 sub_7334 16 sub_8f40 17 sub_95e0 18 ........ 19 sub_a7c4 20 sub_8ee4 21 sub_7334 22 sub_8f40 23 sub_96bc 24 sub_9a2c 25 sub_9268 26 sub_a818 27 sub_a90c 28 sub_a928 29 sub_2b854 30JNIEnv->ReleaseStringUTFChars("FlK6XicivmCwPSE3sk6b71m9WbWd/gYZtlajqGXhEXXjmWEZziR51rVWSEDwUUi4UN9RnoCGbLNmqI80Fiog4Sw==") was called from RX@0x4002b86c[libencrypt.so]0x2b86c

我们的目标大概率是在 0x7000 → 0xa000 这个地址范围之类。

定位Trace Code目标

翻一翻IDA里面的导出表

ida

1:ida

比较合眼缘的就这三个了,0x7184 ,0x77A4 貌似都没有被Trace Block命中,感觉幕后大boss应该就是这个 0x8EE4

Tip: 

可以在怀疑的几个函数上加个hook,看看是否命中。

Unidbg Trace Code

1emulator.attach().addBreakPoint(module.base + 0x8EE4 , new BreakPointCallback() { 2 @Override 3 public boolean onHit(Emulator<?> emulator, long address) { 4 System.out.println(" ====== traceCode ====== "); 5 6 UnidbgPointer pX = UnidbgPointer.register(emulator, Arm64Const.UC_ARM64_REG_X1); 7 8 byte[] bData = pX.getByteArray(0,16); 9 String strLabel = String.format("x0值 0x%08x", pX.peer); 10 Inspector.inspect(bData,strLabel); 11 12 try { 13 emulator.traceCode(module.base + 0x8EE4, module.base + 0x9C0C).setRedirect(new PrintStream(new File("traceCodeCar.log"))); 14 } catch (IOException e) { 15 throw new IllegalStateException(e); 16 } 17 18 return true; 19 }); 20 21}

先在 0x8EE4 加个断点,然后开始Trace Code。 参数是Trace的起始地址和结束地址,已经保存的结果文件。

从这个Trace结果来看,每16个字节去做解密。典型的AES。

Frida Trace Code

Stalker的好处很多,最大的功能是可以过反调试,所以基于Stalker去写Trace Code,写了好几个版本,一直不大满意。

前几天发现了

https://github.com/IIIImmmyyy/frida-trace

比较帅,拿来改了改。效果很好。(只支持Arm64)

1let moduleBase; 2let isFirstIn = true; 3let pre_regs; 4let infoMap = new Map(); 5let detailInsMap = new Map(); 6 7function parserNextAddr(ins) { 8 let s = JSON.stringify(ins); 9 let address = ins.address; 10 // log("address:"+address) 11 let offset = address - moduleBase; 12 let s1 = (offset).toString(16); 13 let entity = {}; 14 entity.address = offset; 15 return s1; 16} 17 18const byteToHex = []; 19for (let n = 0; n <= 0xff; ++n) { 20 const hexOctet = n.toString(16).padStart(2, "0"); 21 byteToHex.push(hexOctet); 22} 23 24function hex(arrayBuffer) { 25 const buff = new Uint8Array(arrayBuffer); 26 const hexOctets = []; 27 for (let i = 0; i < buff.length; ++i) 28 hexOctets.push(byteToHex[buff[i]]); 29 return hexOctets.join(""); 30} 31 32function formatArm64Regs(context) { 33 let regs = []; 34 regs.push(context.x0); 35 regs.push(context.x1); 36 regs.push(context.x2); 37 regs.push(context.x3); 38 regs.push(context.x4); 39 regs.push(context.x5); 40 regs.push(context.x6); 41 regs.push(context.x7); 42 regs.push(context.x8); 43 regs.push(context.x9); 44 regs.push(context.x10); 45 regs.push(context.x11); 46 regs.push(context.x12); 47 regs.push(context.x13); 48 regs.push(context.x14); 49 regs.push(context.x15); 50 regs.push(context.x16); 51 regs.push(context.x17); 52 regs.push(context.x18); 53 regs.push(context.x19); 54 regs.push(context.x20); 55 regs.push(context.x21); 56 regs.push(context.x22); 57 regs.push(context.x23); 58 regs.push(context.x24); 59 regs.push(context.x25); 60 regs.push(context.x26); 61 regs.push(context.x27); 62 regs.push(context.x28); 63 regs.push(context.fp); 64 regs.push(context.lr); 65 regs.push(context.sp); 66 regs.push(context.pc); 67 return regs; 68} 69function getPcReg(regs) { 70 return regs[32]; 71} 72function isRegsChange(context, ins) { 73 let currentRegs = formatArm64Regs(context); 74 let logInfo = ""; 75 for (let i = 0; i < 32; i++) { 76 if (i === 30) { 77 continue; 78 } 79 let preReg = pre_regs[i]; 80 let currentReg = currentRegs[i]; 81 if (Number(preReg) !== Number(currentReg)) { 82 if (logInfo === "") { 83 //尝试读取string 84 let changeString = ""; 85 try { 86 let nativePointer = new NativePointer(currentReg); 87 changeString = nativePointer.readCString(); 88 } 89 catch (e) { 90 changeString = ""; 91 } 92 if (changeString !== "") { 93 currentReg = currentReg + " (" + changeString + ")"; 94 } 95 logInfo = "\t " + getRegsString(i) + " = " + preReg + " --> " + currentReg; 96 } 97 else { 98 logInfo = logInfo + "\t " + getRegsString(i) + " = " + preReg + " --> " + currentReg; 99 } 100 } 101 } 102 //打印PC寄存器 103 let parse = JSON.parse(ins); 104 let mnemonic = parse.mnemonic; //补充str 105 if (mnemonic === "str") { 106 let strParams = getStrParams(parse, currentRegs); 107 logInfo = logInfo + strParams; 108 } 109 else if (mnemonic === "cmp") { 110 let cmpParams = getCmpParams(parse, currentRegs); 111 logInfo = logInfo + cmpParams; 112 } 113 else if (mnemonic === "b.gt" || mnemonic === "b.le" || mnemonic === "b.eq" || mnemonic === "b.ne" || mnemonic === "b") { 114 // log(ins) 115 let bgtAddr = getbgtAddr(parse, currentRegs); 116 logInfo = logInfo + bgtAddr; 117 } 118 let entity = {}; 119 entity.info = logInfo; 120 let address = parse.address; 121 if (lastAddr === undefined) { 122 entity.color = getColor(); 123 lastAddr = address; 124 } 125 else { 126 let number = address - lastAddr; 127 if (number === 0x4) { 128 entity.color = getColor(); 129 } 130 else { 131 currentIndex++; 132 entity.color = getColor(); 133 } 134 lastAddr = address; 135 } 136 pre_regs = currentRegs; 137 return entity; 138} 139let lastAddr = undefined; 140let currentIndex = 0; 141function getColor() { 142 return ""; 143 144 if (currentIndex > 1) { 145 currentIndex = 0; 146 } 147 if (currentIndex === 0) { 148 return "C35"; // logger_1.LogColor.C35; 149 } 150 else if (currentIndex === 1) { 151 return "C97"; // logger_1.LogColor.C97; 152 } 153 else if (currentIndex === 2) { 154 return "C97"; // logger_1.LogColor.C97; 155 } 156} 157function getRegsString(index) { 158 let reg; 159 if (index === 31) { 160 reg = "sp"; 161 } 162 else { 163 reg = "x" + index; 164 } 165 return reg; 166} 167function getbgtAddr(parser, currentRegs) { 168 let bgtAddr = ""; 169 let operands = parser.operands; 170 for (let i = 0; i < operands.length; i++) { 171 let operand = operands[i]; 172 if (operand.type === "imm") { 173 let value = operand.value; 174 let number = value - moduleBase; 175 bgtAddr = "\t block addr:" + number.toString(16); 176 break; 177 } 178 } 179 return bgtAddr; 180} 181function getStrParams(parser, currentRegs) { 182 let operands = parser.operands; 183 for (let i = 0; i < operands.length; i++) { 184 let operand = operands[i]; 185 if (operand.type === "reg") { 186 //获取value 187 let value = operand.value; 188 if (value === "wzr") { 189 return "\t " + "str = 0"; 190 } 191 else { 192 let replace = value.replace("w", ""); 193 let index = replace.replace("x", ""); 194 let index_reg = currentRegs[index]; 195 let changeString = ""; 196 try { 197 let nativePointer = new NativePointer(index_reg); 198 changeString = nativePointer.readCString(); 199 } 200 catch (e) { 201 changeString = ""; 202 } 203 //读取值 204 if (changeString !== "") { 205 index_reg = index_reg + " (" + changeString + ")"; 206 } 207 return "\t " + "str = " + index_reg; 208 } 209 } 210 } 211} 212function getCmpParams(parser, currentRegs) { 213 let operands = parser.operands; 214 let cmpInfo = ""; 215 for (let i = 0; i < operands.length; i++) { 216 let operand = operands[i]; 217 if (operand.type === "reg") { 218 let value = operand.value; 219 let replace = value.replace("w", ""); 220 let index = replace.replace("x", ""); 221 let index_reg = currentRegs[index]; 222 let changeString = ""; 223 try { 224 let nativePointer = new NativePointer(index_reg); 225 changeString = nativePointer.readCString(); 226 } 227 catch (e) { 228 changeString = ""; 229 } 230 //读取值 231 if (changeString !== "") { 232 index_reg = index_reg + " (" + changeString + ")"; 233 } 234 cmpInfo = cmpInfo + "\t " + value + " = " + index_reg; 235 } 236 } 237 return cmpInfo; 238} 239 240function ZY_unTraceAddrEnd(){ 241 var tid = Process.getCurrentThreadId(); 242 Stalker.unfollow(tid); 243 Stalker.garbageCollect(); 244 console.log(TAG + " ======== unTraceAddr_End"); 245} 246 247function ZY_trace_Stalker_begin(soname, addr, size) { 248 let module = Process.findModuleByName(soname); 249 moduleBase = module.base; 250 console.log(TAG + JSON.stringify(module)); 251 console.log(TAG + "addr = " + addr); 252 console.log(TAG + "size = " + size); 253 254 Interceptor.attach(moduleBase.add(addr), { 255 onEnter: function (args) { 256 this.pid = Process.getCurrentThreadId(); 257 // console.log(TAG + " ==== ZY_trace_Stalker_begin ==== "); 258 259 //看下结构体的值 260 Stalker.follow(this.pid, { 261 events:{ 262 call:false, 263 ret:false, 264 exec:false, 265 266 block:false, 267 compile:false 268 }, 269 onReceive:function(events){ 270 }, 271 272 transform: function (iterator) { 273 let lastInfo; 274 const instruction = iterator.next(); 275 let startAddress = instruction.address; 276 // console.log(TAG + "startAddress:" + startAddress + " base:" + module.base ); 277 278 if (size === 0) { 279 size = module.size; 280 addr = 0; 281 } 282 283 const isModuleCode = startAddress.compare(moduleBase.add(addr)) >= 0 && 284 startAddress.compare(moduleBase.add(addr).add(size)) < 0; 285 do { 286 if (isModuleCode) { 287 // console.log(TAG + instruction.address + ":" + instruction); 288 289 let s = parserNextAddr(instruction); 290 let address = instruction.address; 291 let offset = address - moduleBase; 292 let lastInfo = s.toString(16) + "\t\t" + instruction; 293 detailInsMap.set(offset, JSON.stringify(instruction)); 294 infoMap.set(offset, lastInfo); 295 iterator.putCallout(function (context) { 296 let regs = JSON.stringify(context); 297 if (isFirstIn) { 298 isFirstIn = false; 299 //保存寄存器 300 pre_regs = formatArm64Regs(context); 301 } 302 else { 303 //打印的实际是上一次的 这样延迟一次可以打印出寄存器变化 304 let pcReg = getPcReg(pre_regs); 305 let offset = Number(pcReg) - moduleBase; 306 let logInfo = infoMap.get(offset); 307 let detailIns = detailInsMap.get(offset); 308 // log("detailIns:"+detailIns) 309 let entity = isRegsChange(context, detailIns); 310 console.log(TAG + logInfo + " ; " + entity.info, entity.color); 311 } 312 }); 313 } 314 iterator.keep(); 315 } while (iterator.next() != null); 316 }, 317 }); 318 }, 319 onLeave: function (ret) { 320 // libtprt.saveStringMapTofile(); 321 Stalker.unfollow(this.pid); 322 console.log(TAG + "ret:" + ret); 323 } 324 }); 325}

调用方法

ZY_trace_Stalker_begin('libencrypt.so',0x8EF4, 0x9C0C - 0x8EF4);
Tip: 

Trace之前可以先匹配入参,只Trace指定的密文。然后unidbg 去 Trace 同样的密文,这样有利于比对。

对比结果

前戏铺垫的太长了,总算拿到Trace Code的结果了。

main

1:main

这里有个很奇怪的地方,

9608: "ldr w8, [x9, x8, lsl #2]" x9=0x40147510 x8=0x9094 => w8=0xf54de125

0x40147510 这个地址是在so的数据段。 这段代码的意思是 从 0x40147510+(0x9094<<2) = 0x4016B760 这个地址取数据

1m0x4016B760 2 3>-----------------------------------------------------------------------------< 4[11:57:20 551]RW@0x4016b760[libencrypt.so]0x16b760, md5=cc2b1f1f88429f40f84599a613cf3143, hex=25e14df5a6ea8219a148998cebd3d9b5e55522c3109f9bd0347f27854da2729b83f3a390ce0c318645a588f14b339626692918f7119819acb36b0f92cd0dc4d596a74b1bc8f1263f2841c72757b265b4b1692c56a24a9c8f528a41e0d1d95149e25a0120a9e884147d223f7323eb4ff2 5size: 112 60000: 25 E1 4D F5 A6 EA 82 19 A1 48 99 8C EB D3 D9 B5 %.M......H...... 70010: 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

1m0x4016B8EC 2 3>-----------------------------------------------------------------------------< 4[12:00:52 810]RW@0x4016b8ec[libencrypt.so]0x16b8ec, md5=cd71edc06f5f49b56a700c1f6e541610, hex=e35324c7bb9b61e3b06ee0f4f571fb062a44630bc501d369b49e3e2cee56eaeb463ab0c57e5c3b12a8abf8f81264047dd98a871bc1b84e9d807db83241a8ee1d0be8aeecd8e1328a7311b4a73e2c9b925f24946cc35eacb6953472a0b4cdc9baebce5c65701ab7afa0f48251f1e5afab 5size: 112 60000: E3 53 24 C7 BB 9B 61 E3 B0 6E E0 F4 F5 71 FB 06 .S$...a..n...q.. 70010: 2A 44 63 0B C5 01 D3 69 B4 9E 3E 2C EE 56 EA EB *Dc....i..>,.V..

这个地址对应的结果不对。 难道App会在内存中变异?

1m0x4017B8EC 2 3>-----------------------------------------------------------------------------< 4[12:01:51 113]RW@0x4017b8ec[libencrypt.so]0x17b8ec, md5=09b5524828f471a42a3e75beed7b23a8, hex=7253b573f596a2d945a40787a842b3a0e46eadcc761474b12000e2a1ec0ab7053cca47f537ed81c4b823dc9866cc99a7b5b4b4adb3fffa867aa45080ea698ddb04e771872ee075f999b9ba241908bc5ac29666eb5f6a7800abee8213d52a28f19321df19b077a1716dbf3ae72a10f33f 5size: 112 60000: 72 53 B5 73 F5 96 A2 D9 45 A4 07 87 A8 42 B3 A0 rS.s....E....B.. 70010: 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的节表

1fenfei$ greadelf -l libencrypt.so 2 3Elf file type is DYN (Shared object file) 4Entry point 0x3210 5There are 5 program headers, starting at offset 64 6 7Program Headers: 8 Type Offset VirtAddr PhysAddr 9 FileSiz MemSiz Flags Align 10 LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 11 0x000000000002f288 0x000000000002f288 R E 0x10000 12 LOAD 0x000000000002fba0 0x000000000003fba0 0x000000000003fba0 13 0x0000000000194a08 0x0000000000194ab8 RW 0x10000 14 DYNAMIC 0x00000000001c5b90 0x000000000003fc00 0x000000000003fc00 15 0x0000000000000210 0x0000000000000210 RW 0x8 16readelf: Error: no .dynamic section in the dynamic segment 17 GNU_EH_FRAME 0x000000000002cd10 0x000000000002cd10 0x000000000002cd10 18 0x000000000000048c 0x000000000000048c R 0x4 19 LOAD 0x00000000001c5078 0x00000000001d5078 0x00000000001d5078 20 0x0000000000000afc 0x0000000000000afc R E 0x1000

文件地址 0x2fba0 映射到了 内存地址 0x3fba0

所以我们dump出来的so要修复这个映射

1Program Headers: 2 Type Offset VirtAddr PhysAddr 3 FileSiz MemSiz Flags Align 4 LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 5 0x000000000002f288 0x000000000002f288 R E 0x10000 6 LOAD 0x000000000003fba0 0x000000000003fba0 0x000000000003fba0 7 0x0000000000194a08 0x0000000000194ab8 RW 0x10000 8 DYNAMIC 0x00000000001c5b90 0x000000000003fc00 0x000000000003fc00 9 0x0000000000000210 0x0000000000000210 RW 0x8 10readelf: Error: no .dynamic section in the dynamic segment 11 GNU_EH_FRAME 0x000000000002cd10 0x000000000002cd10 0x000000000002cd10 12 0x000000000000048c 0x000000000000048c R 0x4 13 LOAD 0x00000000001c5078 0x00000000001d5078 0x00000000001d5078 14 0x0000000000000afc 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,对比一下更有助于分析算法。

ffshow

1:ffshow

纵浪大化中 不喜亦不惧 应尽便须尽 无复独多虑

Tip: 

: 本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去 奋飞的朋友们 知识星球自取,欢迎加入知识星球一起学习探讨技术。有问题可以加我wx: fenfei331 讨论下。

关注微信公众号: 奋飞安全,最新技术干货实时推送

点赞
收藏

评论区

加载中...

相关推荐

Trace大盘点

一、目标李老板:奋飞呀,最近老听别人说Trace一下,啥是Trace呀?奋飞:老板,先把上次的加班费结算一下。Trace就是在更高抽象层次上去追踪程序的运行流程。二、JNITracejnitrace在Android下混饭吃,首推的就是jnitrace老牌,经典,信息全,携带方便jnitracellibnativelib.socom.example

某汽车社区App 签名和加解密分析 (二) : Frida Dump so

一、目标App安全的主战场在Native层,分析Native层的so,最趁手的兵器就是Frida和Unidbg了。今天我们的目标是某汽车社区Appv8.0.1so的分析。二、步骤特征字符串定位我们在上一篇教程已经定位了,数据加密和解密函数再java层的位置。按照常理来说,这个java类文件中,应该有个System.loadLibrary("

某电商App 返回数据加密解密分析(四)

一、目标最近在抓包某电商App的时候发现一个加密数据,它在做通讯地址请求的时候,请求数据做了加密。返回数据中的地址信息也是密文。今天我们的目标就是这个数据的加密解密。App版本:v10.3.0二、步骤分析一下1、数据的结尾是"",说明是Base64编码,那么我们可以尝试去HookBase64相关函数,然后打印堆栈。2、返回数据格式是json,那么我

某电商App sign签名算法解析(五)

一、目标李老板:奋飞呀,据说某电商App升级了,搞出了一个64位的sign。更牛的是入参都加密了!奋飞:这么拉风,拉出来咱们盘盘。v10.3.2二、步骤32位和64位我们掌握了那么多方法,先搜字符串呢?还是先Hook呢?子曾经曰过:看到32位签名就要想起MD5和HmacSHA1,看到64位签名就要想起HmacSHA256。那就先搞搞java的密码学相关

某车联网App 通讯协议加密分析(二) Unidbg手把手跑通

一、目标有一段时间没有写unidbg相关的文章了,这个样本挺合适,难度适中,还适当给你挖个小坑。所以后面是一个系列文章,包含unidbg补环境,TraceBlock对比流程,TraceCode定位差异。掌握好这一系列套路,Native分析可以算入门了。这次先来把so用unidbg跑通v6.1.0二、步骤DumpsoIDA打开 libencryp

Frida Stalker 是什么?

一、目标在分析so中的算法时,Trace和Debug是常用的手段。了解一些调试器原理的同学都知道,Trace和Debug需要修改原始代码加上个int3,来激活调试器。这样有些App可以依赖检测关键代码来判断是否被调试。也许你会说,我们可以patch掉检测代码,上次飞哥遇到一个狠人app,B去检测A处的代码,C去检测B处的代码,D去检测C处的代码,……反正