首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
13
12
[原创]AI辅助逆向某咖啡白盒AES
发表于: 2025-10-7 00:20
12374
[原创]AI辅助逆向某咖啡白盒AES
nianchu
2025-10-7 00:20
12374
# AI辅助逆向某咖啡白盒AES # 一 分析定位 ### 闲来无事,水个文章 受害者是老演员 某咖啡APP 版本 5.3.97 首先APP为x60企业壳 有frida检测 我采用的是内核模块过的 好了 开始rush b ##### 抓个包先看看参数 如下阔以发现有个q参数 sign参数 ``` sign=7453071242111271530988405881185832853&q=nRMSmZftbACgM2slHBK7oInWjkf2hkOgn-nXF2vFgMZ8TzBce9nuQn90up6rVtw7r6oouj6xaI5gWVt189jMGI58dLeWGKCt2hpcAWebpVcSiGrQcNkemn-0-bQ9ev7wiZ8nDc3B0WMo1h3_39UXBdoUk5OR9JD-PdP8zVgG28YUEd6mLGBnQeAC2Pmz1AeFCSulOGMgDgooLkHQBU_zkTiBWThMufvE7NIWvLxtiD10lOlRFMHoFinzGY9dG-jA00X9bwWB3_sdMKqQr3Hnsz4TWvNFcM7Yqs6SDyvmnYFTAWmSqd1NhRbhgDy1uOfGBhnu7MDZYZ6pZtdUaML5_pDycR-aJouuXxjO_Rs3KUGMcEmuo4Jkf32mGq5bpaXzTxBoSpRAK0UiasbdKFJE35n72fQsleoOt2Tl9Tpwg1MWBLTbcTyuy9uQF9pIsP1Fy8CCQ_ycUI1ppbPPxfqdZZuHQ9ujLk2dzECY5N4dQMvxrnhN7uDL-rEnhKIF-HB_Rb7U_rfVd0LNoO02KTdzhG4VodWG8_rdXStD_KnF9TL0iRqgwqD4SCTZxQmf0NHSAb1oXRTFpXtrxKwDGafthreGaCf6SAbMKfqyR_QAXrpYhm-xwK0HVf1-xxLhbHvWJ8Zirwy1207ZAa67qxJ0DWHHVCsrMXf_Ug47im6LpW0%3D&uid=bfcf236c-1f0b-4517-942a-93d846ef739c1747667865472&t=1759404531524&cid=210101 ``` ##### 那么先确认下加密位置 通过hook java.lang.String 下的一些getBytes方法 得到一些信息 比如登录会提交手机号 所以 追踪我们的手机号看看有没有信息打印 阔以发现 str.getBytesCharset : {"latitude":"29.207122","mobile":"13232323232","countryNo":"86","validateCode":"123456","appversion":"5397"后面省略 这个看起来像是登录提交参数 那很可能是加密明文 于是对信息过滤有手机号 打印堆栈得到以下信息 ##### ``` at java.lang.String.getBytes(Native Method) at java.lang.String.getBytes(String.java:978) at com.luckincoffee.safeboxlib.CryptoHelper.c(SourceFile:7) at com.lucky.lib.http2.HttpAesCrypto.d(SourceFile:1) at com.lucky.lib.http2.AbstractLcRequest.getRequestParams(SourceFile:7) at com.lucky.lib.http2.HttpPostRequestBuilder.k(SourceFile:15) at com.lucky.lib.http2.HttpPostRequestBuilder.getRequest(Unknown Source:0) at com.lucky.lib.http2.AbstractLcRequest.async(SourceFile:5) at com.lucky.lib.http2.AbstractLcRequest.enqueue(SourceFile:3) at com.lucky.lib.http2.HttpPostRequestBuilder.enqueue(Unknown Source:0) at com.lucky.lib.lchttp2rx.result.ReactiveResult$a.a(SourceFile:4) at io.reactivex.internal.operators.observable.ObservableCreate.F5(SourceFile:3) at io.reactivex.Observable.subscribe(SourceFile:4) at io.reactivex.internal.operators.observable.ObservableFlatMap.F5(SourceFile:2) at io.reactivex.Observable.subscribe(SourceFile:4) at io.reactivex.internal.operators.observable.ObservableObserveOn.F5(SourceFile:4) at io.reactivex.Observable.subscribe(SourceFile:4) at com.luckin.luckyclient.account.login.phoneno.LoginPhoneNoPresenter.n2(SourceFile:31) at com.luckin.luckyclient.account.login.phoneno.LoginPhoneNoFragment.e2(SourceFile:4) at com.luckin.luckyclient.account.login.phoneno.LoginPhoneNoFragment.onConfirm(SourceFile:7) at com.luckin.luckyclient.account.login.phoneno.LoginPhoneNoFragment_ViewBinding$b.b(Unknown Source:2) at butterknife.internal.DebouncingOnClickListener.onClick(SourceFile:4) at android.view.View.performClick(View.java:7140) at android.view.View.performClickInternal(View.java:7117) at android.view.View.access$3500(View.java:801) at android.view.View$PerformClick.run(View.java:27351) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) ``` ##### 通过堆栈 at com.luckincoffee.safeboxlib.CryptoHelper.c(SourceFile:7) 查找确定加密点位于以下代码 然后jadx 一键生成frida hook 脚本验证 发现此处hook得到与加密结果一致 ``` public synchronized String c(String str) { c cVar = this.f198397a; if (cVar != null) { String str2 = ""; String a10 = cVar.a(); if (TextUtils.isEmpty(a10)) { return ""; } try { str2 = new String(Base64.encode(localAESWork(str.getBytes(), 2, Base64.decode(a10.replace('-', '+').replace('_', '/').getBytes(), 2)), 2)); } catch (Exception e10) { e10.printStackTrace(); } return str2.replace('+', '-').replace('/', '_'); } throw new RuntimeException(StubApp.getString2("40814")); } ```  ##### 继续追踪代码阔以来到最终加密点 函数localAESWork 然后hook_RegisterNatives 得到so层加密详情如下 com.luckincoffee.safeboxlib.CryptoHelper localAESWork ([BI[B)[B 0x7ce5c4aed8 libcryptoDD.so 0x41ed8 ``` public static native byte[] localAESWork(byte[] bArr, int i10, byte[] bArr2); ``` # 二 搭建unidbg ##### 加密函数找到后 就开始我们的unidbg之旅,首先搭建下架子 ``` package demo.coffe; import com.alibaba.fastjson.util.IOUtils; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Emulator; import com.github.unidbg.Module; import com.github.unidbg.Symbol; import com.github.unidbg.file.FileResult; import com.github.unidbg.arm.HookStatus; import com.github.unidbg.arm.backend.Unicorn2Factory; import com.github.unidbg.arm.context.Arm32RegisterContext; import com.github.unidbg.arm.context.RegisterContext; import com.github.unidbg.debugger.Debugger; import com.github.unidbg.debugger.DebuggerType; import com.github.unidbg.file.FileResult; import com.github.unidbg.file.IOResolver; import com.github.unidbg.hook.HookContext; import com.github.unidbg.hook.ReplaceCallback; import com.github.unidbg.hook.hookzz.Dobby; import com.github.unidbg.hook.hookzz.HookEntryInfo; import com.github.unidbg.hook.hookzz.HookZz; import com.github.unidbg.hook.hookzz.IHookZz; import com.github.unidbg.hook.hookzz.InstrumentCallback; import com.github.unidbg.hook.hookzz.WrapCallback; import com.github.unidbg.hook.xhook.IxHook; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.XHookImpl; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.linux.android.dvm.array.ByteArray; import com.github.unidbg.memory.Memory; import com.github.unidbg.utils.Inspector; import com.github.unidbg.virtualmodule.android.AndroidModule; import com.sun.jna.Pointer; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Base64; public class rxkf extends AbstractJni implements IOResolver { private final AndroidEmulator emulator; private final VM vm; private final Module module; private final boolean logging; rxkf(boolean logging){ this.logging = logging; emulator = AndroidEmulatorBuilder.for64Bit() .setProcessName("com.lucky.luckyclient") .addBackendFactory(new Unicorn2Factory(true)) .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分 final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口 memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析 vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/demo/coffe/kf.apk")); // 创建Android虚拟机 vm.setVerbose(logging); // 设置是否打印Jni调用细节 vm.setJni(this); emulator.getSyscallHandler().addIOResolver(this); new AndroidModule(emulator,vm).register(memory); DalvikModule dm = vm.loadLibrary("cryptoDD", true); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数 module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块 dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 // List<String> funclist = AesKeyFinder.readFuncFromIDA("unidbg-android/src/test/java/demo/one/1.txt"); // AesKeyFinder aesKeyFinder = new AesKeyFinder(emulator); // aesKeyFinder.searchEveryFunction(module.base, funclist); } void destroy() { IOUtils.close(emulator); if (logging) { System.out.println("destroy"); } } public static byte[] hexStringToBytes(String hexString) { if (hexString.isEmpty()) { return null; } hexString = hexString.toLowerCase(); final byte[] byteArray = new byte[hexString.length() >> 1]; int index = 0; for (int i = 0; i < hexString.length(); i++) { if (index > hexString.length() - 1) { return byteArray; } byte highDit = (byte) (Character.digit(hexString.charAt(index), 16) & 0xFF); byte lowDit = (byte) (Character.digit(hexString.charAt(index + 1), 16) & 0xFF); byteArray[i] = (byte) (highDit << 4 | lowDit); index += 2; } return byteArray; } public static String bytesTohexString(byte[] bytes) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xFF); if (hex.length() < 2) { sb.append(0); } sb.append(hex); } return sb.toString(); } public static String b64TohexString(String base64String){ byte[] decodedBytes = org.apache.commons.codec.binary.Base64.decodeBase64(base64String); BigInteger bigInteger = new BigInteger(1, decodedBytes); String hexString = bigInteger.toString(16); return hexString; } public void trace(){ String traceFile = "unidbg-android/src/test/java/demo/coffe/trace.txt"; PrintStream traceStream = null; try{ traceStream = new PrintStream(new FileOutputStream(traceFile), true); } catch (FileNotFoundException e) { e.printStackTrace(); } //核心 trace 开启代码,也可以自己指定函数地址和偏移量 emulator.traceCode(module.base,module.base+module.size).setRedirect(traceStream); } public static void main(String[] args) { rxkf demo = new rxkf(true); demo.s(); } public void s(){ DvmClass CryptoHelper = vm.resolveClass("com/luckincoffee/safeboxlib/CryptoHelper"); //DvmObject<?> Context = vm.resolveClass("android/content/Context").newObject(null); byte[] bytes = "hello".getBytes(); String a2 = "u7Su25kSE9PxcTgQZkRgL0kJ+lDaV2IQcqdsfGGuNDs=" ; byte[] bytes2 = Base64.getDecoder().decode(a2.replace('-', '+').replace('_', '/').getBytes()); // emulator.traceCode(module.base,module.base+module.size); ByteArray reval = CryptoHelper.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B", bytes,2,bytes2); System.out.println("结果:" + bytesTohexString(reval.getValue())); } @Override public FileResult resolve(Emulator emulator, String pathname, int oflags) { System.out.println("pathname:"+ pathname); // if(pathname.equals("/proc/"+emulator.getPid()+"/cmdline")){ // return FileResult.success(new SimpleFileIO(oflags, new File("unidbg-android/src/test/java/demo/wb/cmdline"), pathname)); // // } return null; } } ``` 先跑一下再说 发现报错了 去到0x43b08 处 是一个free函数 那么可能是 Unidbg在内存管理方面存在的一些问题,此处是在内存的释放上 free出错。 ``` JNIEnv->SetByteArrayRegion([B@6aaa5eb0, 0, 16, RW@0x40399010) was called from RX@0x40043b08[libcryptoDD.so]0x43b08 [19:53:15 655] WARN [com.github.unidbg.arm.AbstractARM64Emulator] (AbstractARM64Emulator$1:66) - Write memory failed: address=0x0, size=8, value=0x40057000 [19:53:15 655] WARN [com.github.unidbg.AbstractEmulator] (AbstractEmulator:420) - emulate RX@0x40041ed8[libcryptoDD.so]0x41ed8 exception sp=unidbg@0xbffff3b0, msg=unicorn.UnicornException: Invalid memory write (UC_ERR_WRITE_UNMAPPED), offset=10ms ``` 针对此处报错 我们先对free处理看看能否成功跑起来,hook free函数,替换它的实现,让它什么都不做就直接返回。 处理后 unidbg已经跑出了结果 frida hook 传参 并且传入 与抓包对比一致 no problem 下一步开始分析算法,这个案例老版本有很多文章了,阔以知道 这个家伙用的是 白盒aes ida打开so 去到localAESWork 函数处先瞅一眼 ``` public void patchFree(){ IWhale whale = Whale.getInstance(emulator); Symbol free = emulator.getMemory().findModule("libc.so").findSymbolByName("free"); whale.inlineHookFunction(free, new ReplaceCallback() { @Override public HookStatus onCall(Emulator<?> emulator, long originFunction) { System.out.println("WInlineHookFunction free=" + emulator.getContext().getPointerArg(0)); return HookStatus.LR(emulator, 0); } }); } ``` 不看不知道 一看全是线条 ollvm 玩个锤子 奈子的 trace 它吧  先固定传参 方便后续处理我们的aes 传个hello 加密 结果是 fa63a50fd234b8c15a8f4ff9761ea69a 然后开启我们的trace 会再文件夹生成trace文本 ``` public static void main(String[] args) { rxkf demo = new rxkf(true); demo.patchFree(); demo.trace(); demo.s(); } ``` 然后在我们的trace结果中搜索我们的结果 fa63a50fd234b8c15a8f4ff9761ea69a 搜下 0xfa 发现结果有点多 不好看 继续往后找 发现搜素 0x8f 结果比较少 比较好观察 于是 前后观察下 发现 我们生成结果都在 于是找个地址 跳过去看看 这里找的0x199e0   阔以发现 ollvm 严重影响我们分析 那么不还原ollvm 我们就只能硬着头皮看看 主要看函数 跟进去看看 瞧瞧 瞅瞅 他们都干啥了 那么此处 你需要先去了解AES的算法 然后还有白盒AES。由于论坛已经有很多写的好的文章了,这里就不提哪么详细,那么有人问 我没了解AES算法怎么办 别怕 我们有小帮手AI 毕竟现在是AI辅助逆向时代 我们往下继续找函数  我们进 219CC 里面去看看 发现是这个鸟样子 看不懂怎么办 不怕 问下ai ai猜猜猜他在干啥  经过AI的一顿分析 喀喀喀的就出来了我们的结果 它说是 **AES加密中的轮密钥加(AddRoundKey)操作** 我们继续往下看几个 比如21BEC   这个函数 我们阔以看到有个byte_455E8 它频繁的从里面取了值 我们去看看byte_455E8 这不是s盒吗 看这个操作从s盒取值 然后后面又赋值给result 符合我们AES里面的**SubBytes** 操作 那不问ai了 继续往下看 223E0   223E0 函数 有个很明显的换位操作 符合AES里面的 **ShiftRows** 继续看看22810 直接 丢给ai看   说这是列混淆 去trace里面看看这个函数 调用几次  阔以看到是9次 AES-128 中 10轮运算 只有9轮有列混淆 经过AI辅助(不一定正确),我们已经找出了AES的这些步骤 接下来就可以进行DFA攻击了 故障点应该让发生在倒数两个列混淆之间 - ✅ **SubBytes** - 字节替换 - ✅ **ShiftRows** - 行移位 - ✅ **MixColumns** - 列混淆 - ✅ **AddRoundKey** - 轮密钥加 攻击之后 得到的故障密文 与 正确密文 对比如下 可以发现四个字节不一样 说明故障注入成功 ``` public void dfaAttack(){ emulator.attach().addBreakPoint(module.base + 0x22810, new BreakPointCallback() { int count = 0; UnidbgPointer pointer; @Override public boolean onHit(Emulator<?> emulator, long address) { count += 1; RegisterContext registerContext = emulator.getContext(); pointer = registerContext.getPointerArg(0); emulator.attach().addBreakPoint(registerContext.getLRPointer().peer, new BreakPointCallback() { @Override public boolean onHit(Emulator<?> emulator, long address) { if(count % 8 == 0){ pointer.setByte(randInt(0, 15), (byte) randInt(0, 0xff)); } return true; } }); return true; } }); } ============================ 正确:fa63a5 0f d234 b8 c15a 8f 4ff9 76 1ea69a 故障:fa63a5 36 d234 f4 c15a dd 4ff9 43 1ea69a 可以发现4处不同 ``` 然后循环注入得到更多的故障密文,使用 phoenixAES 把第十轮的密钥给计算出来 得到  然后使用 <a href="elink@92fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6e0K9h3c8W2b7$3S2S2L8X3&6W2L8p5#2S2M7Y4k6W2L8s2y4Q4x3V1k6e0N6r3q4J5K9#2)9J5c8X3u0D9L8$3u0Q4x3V1k6E0j5i4y4@1k6i4u0Q4x3V1k6S2k6i4y4Q4y4h3k6C8k6i4W2K6j5$3S2W2k6s2g2D9k6g2)9J5k6h3x3`.">aes_keyschedule</a> 去计算出原始密钥  有了key之后 我们去解密一下 首先密文需要replace('-', '+').replace('_', '/')); 替换下 然后去解密可以正常解密 
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#逆向分析
收藏
・
13
点赞
・
12
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
沐仁浴义
感谢你的贡献,论坛因你而更加精彩!
2026-4-7 23:37
wx_momo_526
你的帖子非常有用,感谢分享!
2026-3-27 17:09
seeeseee
你的帖子非常有用,感谢分享!
2025-11-24 10:30
程序原
+1
非常支持你的观点!
2025-10-18 07:31
iCookiie
期待更多优质内容的分享,论坛有你更精彩!
2025-10-17 09:26
Ally Switch
+1
谢谢你的细致分析,受益匪浅!
2025-10-13 15:26
clz3216
感谢你分享这么好的资源!
2025-10-11 11:51
mb_shzsxtje
期待更多优质内容的分享,论坛有你更精彩!
2025-10-10 10:52
sinker_
非常支持你的观点!
2025-10-10 07:14
aizhiqun
谢谢你的细致分析,受益匪浅!
2025-10-8 22:14
doduhuang
你的帖子非常有用,感谢分享!
2025-10-7 09:51
pexillove
为你点赞!
2025-10-7 01:02
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
4
)
mb_widawacg
雪 币:
224
活跃值:
(10)
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
5
粉丝
0
关注
私信
mb_widawacg
2
楼
有没有想兼职挖洞的高手啊,qq 28114822
2025-10-11 11:24
0
不具名的悲伤
雪 币:
3
活跃值:
(1790)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
123
粉丝
0
关注
私信
不具名的悲伤
3
楼
6
2025-10-14 10:45
0
QQ765746002
雪 币:
32
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
5
粉丝
0
关注
私信
QQ765746002
4
楼
老师,想咨询下,如果某个app服务器下线了,还有可能逆出来吗
2025-10-15 12:03
0
结城希亚
雪 币:
729
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
5
粉丝
3
关注
私信
结城希亚
5
楼
师傅可以加个联系方式吗????
2025-10-30 11:08
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
nianchu
4
发帖
8
回帖
30
RANK
关注
私信
他的文章
[原创]某咖啡(二)sign参数逆向分析
11190
[原创]AI辅助逆向某咖啡白盒AES
12374
[分享]某企业壳frida检测绕过(二)
10409
[分享]某邦企业壳frida检测绕过
13720
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部