首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
0
1
[原创]KCTF 第一题(AliCrackme_1 逆向分析过程)
发表于: 2天前
163
[原创]KCTF 第一题(AliCrackme_1 逆向分析过程)
苏泽001
2天前
163
# AliCrackme_1 逆向分析过程(jadx 全局搜索法) > 目标:`AliCrackme_1.apk` 包名:`com.example.simpleencryption` 工具:jadx + Frida(可选) 密码:**`581026`** ## 一、思路:以"提示字符串"为锚点反向定位代码 不通读代码,先找一个**一眼能认出的锚点**,再顺着引用反推到核心校验逻辑。 锚点优先级:**用户可见文案(弹窗/按钮/日志)> 资源 id > 方法名 > 变量名**。 ## 二、具体步骤 ### 步骤 1:全局搜索"密码" 命中资源字符串: ```xml <string name="dialog_error_tips">密码不对,请继续破解</string> ``` ### 步骤 2:全局搜索资源名 `dialog_error_tips` 把字符串 id 当关键字再搜一次,命中**唯一引用点**——按钮 `onClick` 的失败分支: ```java if (pw == null || pw.equals("") || !pw.equals(enPassword)) { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setMessage(R.string.dialog_error_tips); // ← 就是这里 builder.setTitle(R.string.dialog_title); builder.setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.show(); return; } ``` ### 步骤 3:从失败分支往上读,还原完整校验逻辑 ```java public void onClick(View v) { String password = edit.getText().toString(); // ① 用户输入 String table = MainActivity.this.getTableFromPic(); // ② 查表(256 项) String pw = MainActivity.this.getPwdFromPic(); // ③ 正确答案(密文) Log.i("lil", "table:" + table); Log.i("lil", "pw:" + pw); String enPassword = ""; try { enPassword = MainActivity.bytesToAliSmsCode(table, password.getBytes("utf-8")); // ④ 加密输入 Log.i("lil", "enPassword:" + enPassword); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if (pw == null || pw.equals("") || !pw.equals(enPassword)) { // 失败:密码不对,请继续破解 return; } MainActivity.this.showDialog(); // 成功:恭喜!!!破解成功!!! } ``` **归纳**:把「用户输入的 UTF-8 字节」按 `table` 查表得到 `enPassword`,再与 `getPwdFromPic()` 的返回值 `pw` 做**字符串相等比较**。 ### 步骤 4:深挖三个关键函数 **4.1 `getPwdFromPic()` —— 正确答案(密文)从哪来** ```java is = getResources().getAssets().open("logo.png"); // 注意是 assets/ 下那张 logo.png int lenght = is.available(); byte[] b = new byte[lenght]; is.read(b, 0, lenght); byte[] data = new byte[18]; System.arraycopy(b, 91265, data, 0, 18); // 硬编码偏移 91265, 长度 18 String value2 = new String(data, "utf-8"); ... value = value2; // ← 最终返回的 value ``` **4.2 `getTableFromPic()` —— 同一个文件,换一段偏移** ```java byte[] data = new byte[768]; System.arraycopy(b, 89473, data, 0, 768); // 偏移 89473, 长度 768 ``` 同样按硬编码偏移从 `assets/logo.png` 抠数据,UTF-8 解码后是 **256 个汉字**。 **4.3 `bytesToAliSmsCode()` —— 核心算法** ```java private static String bytesToAliSmsCode(String table, byte[] data) { StringBuilder sb = new StringBuilder(); for (byte b : data) { sb.append(table.charAt(b & 255)); // 字节值(0~255) 当下标去查表 } return sb.toString(); } // 类里还留了个反向函数(本题未被调用, 但暴露了作者意图) private static byte[] aliCodeToBytes(String codeTable, String strCmd) { byte[] cmdBuffer = new byte[strCmd.length()]; for (int i = 0; i < strCmd.length(); i++) { int v = codeTable.indexOf(strCmd.charAt(i)); // 字符 → 表内下标 cmdBuffer[i] = (byte) v; } return cmdBuffer; } ``` **本质**:`table` 是一张 **256 项的"字节 → 字符"替换表**;`bytesToAliSmsCode` 是加密(正查),`aliCodeToBytes` 是解密(反查)。 ## 三、怎么把 `pw` 变成密码 ### 3.1 Hook 取数 代码里其实**已经埋好了日志**,最省事的是直接看 logcat: ```bash adb logcat -s lil # 或 adb logcat lil:I *:S ``` 想现场求值就用 Frida(脚本:[`scripts/hook_alicrackme.js`](/Users/liuwei/Desktop/MyProject/tasks/AliCrackme逆向-2026-09-15/scripts/hook_alicrackme.js)): ```javascript var MainActivity = Java.use("com.example.simpleencryption.MainActivity"); var origPwd = MainActivity.getPwdFromPic; // 先存原方法引用! MainActivity.getPwdFromPic.implementation = function () { var v = origPwd.call(this); // 不能写 this.getPwdFromPic(), 会无限递归 console.log("[+] getPwdFromPic() => " + v); return v; }; ``` ### 3.2 ⚠️ 关键修正:hook 到的 `value` 是**密文**,不是密码 `getPwdFromPic()` 返回的 `pw` 是**正确答案经过查表加密后的密文**,不能直接填进输入框—— 因为你的输入会被 `bytesToAliSmsCode` **再加密一次**,永远不可能等于 `pw` 本身。 ``` table = "一乙二十丁厂七卜人入八九几儿了…令用甩印乐" // 256 个汉字 pw = "义弓么丸广之" // 密文, 18 字节 UTF-8 = 6 个汉字 ``` **正确做法是反查**(即 `aliCodeToBytes` 的思路):对 `pw` 每个汉字用 `table.indexOf(字)` 取下标,下标就是原始字节。 | 密文字符 | `table.indexOf()` | 字节 | 字符 | |---|---|---|---| | 义 | 53 | 0x35 | `5` | | 弓 | 56 | 0x38 | `8` | | 么 | 49 | 0x31 | `1` | | 丸 | 48 | 0x30 | `0` | | 广 | 50 | 0x32 | `2` | | 之 | 54 | 0x36 | `6` | → `35 38 31 30 32 36` → **`581026`** > 能反查的前提:`table` 去重后**仍是 256 个不重复字符**,0~255 每个字节值都有唯一对应字符(全排列/双射)。若表有重复项,`indexOf` 只能取首次出现的下标,就解不出唯一答案。 ### 3.3 回代校验 ``` bytesToAliSmsCode(table, "581026".getBytes("utf-8")) == "义弓么丸广之" ✅ ``` 等价于:输入 `581026` 点登录,日志 `enPassword:义弓么丸广之` 与 `pw:义弓么丸广之` 一致,弹窗 `恭喜!!!破解成功!!!`。 ## 四、备用方案:纯静态提取(不用设备) ```python raw = open("assets/logo.png", "rb").read() table = raw[89473:89473 + 768].decode("utf-8") # 256 个汉字 pw = raw[91265:91265 + 18].decode("utf-8") # "义弓么丸广之" password = bytes(table.index(c) for c in pw) # b"581026" ``` > 这两段数据藏在 `assets/logo.png` 的 `IEND` chunk(偏移 89465)**之后**,属于"PNG 尾部追加数据"的隐写写法,看图工具完全看不到,必须按字节偏移取。 ## 五、流程与结论 ``` 全局搜索"密码" → 命中 dialog_error_tips 字符串 → 全局搜索 dialog_error_tips → 命中 onClick 失败分支 → 往上读还原完整校验逻辑 → 取 table(logo.png[89473,768)) + pw(logo.png[91265,18)) → table.indexOf 反查密文 → 字节 35 38 31 30 32 36 → 密码 = 581026 ``` | 项目 | 结果 | |---|---| | 密码 | **581026** | | 校验方式 | 查表替换后做字符串相等比较 | | 数据藏匿位置 | `assets/logo.png` 的 IEND 之后(偏移 89473 / 91265) | | 是否需要动态调试 | **不需要**,纯静态可解;hook 仅用于验证 | **经验总结** 1. **锚点法**:先用可见文案做全局限定搜索,比通读代码快得多。 2. **跟资源 id 的引用**:`R.string.xxx` 往往只被一处引用,等于直达关键分支。 3. **对硬编码偏移要敏感**:`arraycopy(b, 91265, ...)` 这类"魔法数字"是藏数据最典型的手法。 4. **先判断算法可逆性**:查表类算法看表有无重复项,全排列可直接反查。 5. **hook 前先看日志**:本题已埋 `Log.i("lil", ...)`,`adb logcat` 就能拿到中间值。 ---
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
mb_arjunooq
期待更多优质内容的分享,论坛有你更精彩!
1天前
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
苏泽001
4
发帖
6
回帖
10
RANK
关注
私信
他的文章
[原创]KCTF 第一题(AliCrackme_1 逆向分析过程)
163
[原创]房天下APP搜索接口wirelesscode参数逆向
11403
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部