2020春节红包第三题-Xposed框架Hook的应用 By:CrazyNut
本帖最后由 CrazyNut 于 2020-2-9 04:44 编辑## 准备工具以及思路
**首先不了解Xposed框架Hook的可以看看大佬的基础教程 - [《教我兄弟学Android逆向12 编写xposed模块》](https://www.52pojie.cn/thread-850885-1-1.html)**
本文不需要会看懂汇编代码,当你看完上面的文章,学会Xposed框架Hook的简单应用后。
就算是从未接触过的萌新跟着流程一会就能搞定,一起来动手吧!
**准备工具**
1.Android Studio - 以及Xposed框架环境 - 有关Xposed框架环境的搭建和编译在上述帖子都有,我就不在赘述
2.DDMS - 用于查看打印出来的日志信息,有Android Studio配套就有
3.**(https://www.lanzouj.com/i96dr2h)** - 用于将Dex文件还原为jar
4.**(https://www.lanzouj.com/i96drza) **- 用于查看jar包的java代码
5.**(https://down.52pojie.cn/Tools/Disassemblers/IDA.txt)** - 用于查看So文件代码
6.一款可以打开Apk的压缩软件
7.一个装有Xposed环境的安卓模拟器或者手机
**思路**
1.首先将APK的dex还原为jar后,可以用jd-gui查看java代码
2.后面可以发现关键代码都在So文件内[剧透] ,然后用IDA打开So文件,找到对应的关键函数
3.加以以分析Hook某关键函数后直接出结果
## 查看APK的java代码
### Dex还原为jar
用压缩软件打开APK可以看到classes.dex文件
!(https://i.loli.net/2020/02/09/ixD6YlptsdZG8er.jpg)
将其解压并扔到dex2jar的根目录下,运行 DexToJar.bat
!(https://i.loli.net/2020/02/09/7TKDCezcS1GAwuZ.jpg)
即可把dex文件还原为Jar
!(https://i.loli.net/2020/02/09/TQxG9bwLu4AoqDC.jpg)
上图红框则是我们需要的文件
### 用Jd-gui查看java代码
然后将其直接拖入jd-gui即可看到java代码
!(https://i.loli.net/2020/02/09/a14ySKH6Y2sCQPl.jpg)
CM代码很少,很容易看到入口函数MainActivity里的代码
这段java代码里面没有任何关键的代码
有一点点安卓基础就看的出来,APK引用了一个叫libcrack_j2c的so文件,所以可以判断关键代码全在so里面
~~~
而且作者方法名都明说了是checkFlag。。 可以看到参数时一个String 肯定是传入的相关flag 返回值也是个bool值,肯定是关键函数咯
当然 因为按钮只有一个搜OnClick也是一样的,从按钮事件入手。
~~~
所以直接上IDA分析so文件找到checkFlag或者OnClick这个函数。
## IDA查看so文件
### 定位到关键函数代码 - 方法 1.字符串大法好
直接找到
验证错误,继续加油
一步到位,香的不行
我的IDA版本暂时搜不了字符串,后面补上。。
### 定位到关键函数代码 - 方法 2.直接搜方法名
上面说到了 作者已经明确的告诉了checkFlag和onclick了这函数了。。明摆着就是关键函数。
所以在so文件没被混淆的情况下,可以直接用IDA打开So文件,搜索方法 checkFlag 或者 onclick
!(https://i.loli.net/2020/02/09/CdyNHoDbzJSsUq7.jpg)
IDA中直接拖入so载入后,在Functions Window 按ctrl+f直接搜方法名一步到位
**不过这里是不推荐用这个方法的。。因为就算So文件没被混淆
正常项目代码量巨大。。根本不知道谁是谁。。。不容易直接一眼就看出来的情况下
这个方法无疑大海捞针,所以下面介绍另一种方法**
### 定位到关键函数代码 - 方法 3.Xposed框架Hook打印堆栈
这里就需要用到Xposed框架来Hook了,还不会请先看上方基础教程
这里直接贴上一段代码 - [《利用Xposed Hook打印Java函数调用堆栈信息的几种方法》](https://blog.csdn.net/QQ1084283172/article/details/79378374)
~~~
具体思路是:既然按了验证按钮后有土司 提示长度不够,那我直接Hook Toast()这个方法 看看什么调用了他,不就可以找到点击事件了?
虽然对这题有点脱了裤子放屁。。。不过这也是一种思路吧
~~~
自己修改对应包名后变成如下
~~~
findAndHookMethod(
"android.widget.Toast", //要hook的包名+类名
lpparam.classLoader, //classLoader固定
"makeText", //要hook的方法名
// 方法参数 没有就不填
Context.class,
CharSequence.class,
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//方法执行前执行
Log.i("Nut", "NutHookToast:" + param.args.toString());
}
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//方法执行后执行,改方法的返回值一定要在方法执行完毕后更改
// 方法一:
Log.i("Dump Stack: ", "---------------start----------------");
Throwable ex = new Throwable();
StackTraceElement[] stackElements = ex.getStackTrace();
if (stackElements != null) {
for (int i = 0; i < stackElements.length; i++) {
Log.i("Dump Stack"+i+": ", stackElements.getClassName()
+"----"+stackElements.getFileName()
+"----" + stackElements.getLineNumber()
+"----" +stackElements.getMethodName());
}
}
Log.i("Dump Stack: ", "---------------over----------------");
}
}
);
~~~
Hook后 进去 随便按一下 验证按钮 得到日志如下
!(https://i.loli.net/2020/02/09/J8tWQxK5olD3G1U.jpg)
可以看出只有一个包名为 com.wuaipojie.crackme01 的方法被调用 正是onclick的按钮事件。
然后用方法2直接IDA搜方法就可以了
## 简单分析函数代码
### 按钮OnClick事件方法
这里先从按钮事件开始
搜索onclick找到函数后,汇编代码是看不懂的。。。所以直接按F5查看伪代码,一起就清晰了起来
下面是代码
~~~
int __fastcall Java_com_wuaipojie_crackme01_MainActivity_onClick__Landroid_view_View_2(int a1, int a2, int a3)
{
int *v3; // r4
int v4; // r5
int v5; // r9
int v6; // r0
int v7; // r5
int (__fastcall *v8)(int *, void *); // r2
int v9; // r6
int v10; // r8
int v11; // r6
int v12; // r8
int v13; // r5
int v14; // r5
void *v15; // r1
int v16; // r6
int v17; // r5
int result; // r0
int v19; //
int v20; //
int v21; //
int v22; //
int v23; //
int v24; //
int v25; //
int v26; //
int v27; //
int v28; //
int v29; //
int v30; //
int v31; //
int v32; //
int v33; //
int v34; //
int v35; //
v3 = (int *)a1;
v4 = a3;
v31 = 0;
v29 = 0;
v30 = 0;
v27 = 0;
v28 = 0;
v25 = 0;
v26 = 0;
v23 = 0;
v24 = 0;
v21 = 0;
v22 = 0;
v19 = 0;
v20 = 0;
v5 = (*(int (__fastcall **)(int, int))(*(_DWORD *)a1 + 100))(a1, a2);
v6 = (*(int (__fastcall **)(int *, int))(*v3 + 100))(v3, v4);
if ( !v5 )
goto LABEL_38;
v7 = v6;
if ( sub_539C(v3, &v31, &v26, 0, "com/wuaipojie/crackme01/MainActivity", "editText", "Landroid/widget/EditText;") )
goto LABEL_39;
v9 = (*(int (__fastcall **)(int *, int, int))(*v3 + 380))(v3, v5, v26);
if ( (*(int (__fastcall **)(int *))(*v3 + 912))(v3) )
goto LABEL_39;
if ( v7 )
(*(void (__fastcall **)(int *, int))(*v3 + 92))(v3, v7);
if ( !v9 )
goto LABEL_38;
if ( !v25 && sub_5288(v3, &v30, &v25, 0, (int *)"android/widget/EditText", "getText") )
goto LABEL_39;
v10 = (*(int (__fastcall **)(int *, int))(*v3 + 144))(v3, v9); //获取输入框中的文本 存到v10中
if ( (*(int (__fastcall **)(int *))(*v3 + 912))(v3) )
goto LABEL_39;
(*(void (__fastcall **)(int *, int))(*v3 + 92))(v3, v9);
if ( !v10 )
goto LABEL_38;
if ( !v24 && sub_5288(v3, &v29, &v24, 0, (int *)"java/lang/Object", "toString") )
goto LABEL_39;
v11 = (*(int (__fastcall **)(int *, int))(*v3 + 144))(v3, v10); //v10 tostring一下 存到 v11
if ( (*(int (__fastcall **)(int *))(*v3 + 912))(v3) )
goto LABEL_39;
(*(void (__fastcall **)(int *, int))(*v3 + 92))(v3, v10);
if ( !v11 )
goto LABEL_38;
if ( !v23 && sub_5288(v3, &v28, &v23, 0, (int *)"java/lang/String", "trim") )
goto LABEL_39;
v12 = (*(int (__fastcall **)(int *, int))(*v3 + 144))(v3, v11); //v11 再trim一次 存到 v12
if ( (*(int (__fastcall **)(int *))(*v3 + 912))(v3) )
goto LABEL_39;
(*(void (__fastcall **)(int *, int))(*v3 + 92))(v3, v11);
if ( !v12 )
goto LABEL_38;
if ( !v22 && sub_5288(v3, &v28, &v22, 0, (int *)"java/lang/String", "length") )
goto LABEL_39;
v13 = (*(int (__fastcall **)(int *, int))(*v3 + 204))(v3, v12); //获取v12的长度 存到 v13
if ( (*(int (__fastcall **)(int *))(*v3 + 912))(v3) ) //【其实上面的一堆就是获取了你输入的flag的长度】
goto LABEL_39;
if ( v13 == 30 ) //这里判断 v13 是否为 30也就是判断flag长度是否为30
{
//如果长度正确 则进入下面 checkflag的判断流程
if ( !v21 && sub_5288(v3, &v31, &v21, 0, (int *)"com/wuaipojie/crackme01/MainActivity", "checkFlag") )
goto LABEL_39;
v32 = v12;
v14 = (*(int (__fastcall **)(int *, int))(*v3 + 156))(v3, v5);
if ( (*(int (__fastcall **)(int *))(*v3 + 912))(v3) )
goto LABEL_39;
(*(void (__fastcall **)(int *, int))(*v3 + 92))(v3, v12);
v8 = *(int (__fastcall **)(int *, void *))(*v3 + 668);
if ( !v14 )
goto LABEL_40;
v15 = &unk_22047;
}
else
{
(*(void (__fastcall **)(int *, int))(*v3 + 92))(v3, v12);
v15 = &unk_2211B;
v8 = *(int (__fastcall **)(int *, void *))(*v3 + 668);
}
while ( 1 )
{
v16 = v8(v3, v15);
if ( v20 || !sub_5288(v3, &v27, &v20, 1, (int *)"android/widget/Toast", "makeText") )
{
v33 = v16;
v32 = v5;
v34 = 0;
v17 = (*(int (__fastcall **)(int *, int))(*v3 + 464))(v3, v27);
if ( !(*(int (__fastcall **)(int *))(*v3 + 912))(v3) )
{
if ( v16 )
(*(void (__fastcall **)(int *, int))(*v3 + 92))(v3, v16);
if ( v17 )
{
if ( v19 || !sub_5288(v3, &v27, &v19, 0, (int *)"android/widget/Toast", "show") )
{
(*(void (__fastcall **)(int *, int))(*v3 + 252))(v3, v17);
(*(void (__fastcall **)(int *))(*v3 + 912))(v3);
}
}
else
{
LABEL_38:
sub_4EC0(v3, "java/lang/NullPointerException", "NullPointerException");
}
}
}
LABEL_39:
result = _stack_chk_guard - v35;
if ( _stack_chk_guard == v35 )
break;
LABEL_40:
v15 = &unk_220FF;
}
return result;
}
~~~
**上面代码已经备注了
可以看到flag 长度要为 30 【欸! 这不是废话吗,已经提示了好不】
就会把flag传入 checkflag这个方法进行判断**
所以接下来是 checkflag 这个方法
### checkflag方法
和上面同样的方法 找到这个方法后F5一下 直接看伪代码
这一段代码就算是F5的伪代码 算法部分我不是很看得懂。。连蒙带猜的做的吧。。
坐等大佬们更详细的分析过程噢,这里说一下我的思路。
因为看不懂具体代码了,所以我考虑从Hook函数中所调用的方法入手
看了几遍后 发现函数最下方的 代码部分最可疑
!(https://i.loli.net/2020/02/09/GZraoLIchWtENfs.jpg)
可以看到 一次调用了 ToString substring equals方法
~~~
之前在看到flag是30位的时候
我就一直在猜想,似乎没有什么加密是30位的啊?? 所以估计是密钥加密后进行了切割。
~~~
这三个函数应该就认证了我的猜想。
~~~
上面的一堆函数估计是对密钥进行加密
toString 之后 马上用 SubString 进行切割
最后用equals方法比较。。。
所以我决定把这三个函数全部Hook打印结果
以上只是猜想,有理解错误的地方请多指正
~~~
## 见证奇迹的Hook
最后Hook的结果证实 对 SubString 的 Hook 确实有用 [剧透]
所以下面只上对 substring 这个方法Hook 的 函数
~~~
findAndHookMethod(
"java.lang.String", //要hook的包名+类名
lpparam.classLoader, //classLoader固定
"substring", //要hook的方法名
// 方法参数 没有就不填
int.class,
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//方法执行前执行
Log.i("Nut", "substringA 参数A:" + param.args+"参数B:"+param.args);
}
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//方法执行后执行,改方法的返回值一定要在方法执行完毕后更改
Log.i("Nut", "substringA:" + param.getResult());
}
}
);
//因为substring有两种重载 所以我无脑一起hook了。。
findAndHookMethod(
"java.lang.StringBuilder", //要hook的包名+类名
lpparam.classLoader, //classLoader固定
"substring", //要hook的方法名
// 方法参数 没有就不填
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//方法执行前执行
Log.i("Nut", "substringB 参数A:" + param.args);
}
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//方法执行后执行,改方法的返回值一定要在方法执行完毕后更改
Log.i("Nut", "substringB:" + param.getResult());
}
}
);
~~~
**随便输一个30位的flag之后验证当当当当 出来了**
!(https://i.loli.net/2020/02/09/Ey6eObqStlXTmnM.jpg)
Log如下
~~~
02-09 04:00:34.060: I/Nut(2098): Already Find Methed
02-09 04:00:34.062: I/Nut(2098): substringA 参数A:1参数B:31
02-09 04:00:34.062: I/Nut(2098): substringA:ed61f6308c74bcf35c71729d4db24c
02-09 04:00:34.062: I/Nut(2098): NutHookToast:验证错误,继续加油
~~~
**可以看到确实是32位的加密 截取了 中间的 1-31位
结果也直接就打印出来了!!最后的equals也就是做了一个字符串比较而已。**
**本题也就此解开! 完结 撒花~**
## 总结
**星球杯大佬说过 : Xposed用的好就是可以为所欲为!
想想自己不知道还要隔多久才能到达这种高度,不禁有些小伤感 QAQ**
**本题也还有很多没弄懂的地方,期待大佬们出更详细的教程!
期待大佬们第四题的详细分析噢!希望能学习一下!**
**谢谢大佬们阅读 , 如有错误的地方请帮忙指正,以免误人子弟!谢谢** 拖入GDA一看,发现关键函数onclick是空的,看MainActivity的oncreate,发现so,拿出ida动态调试,看符号发现onclick,进去,F5,发现关键函数是checkflag,再进去,结尾处发现string equal,疑似最终判断,call处下断点,修改汇编使得string.equal变为java层字符串转C层的函数,随便输入断下,修改寄存器里的参数,在单步步过,跳转到R0的地址就是密码了
这个题我奋战了3天吧,安卓逆向从0到有,感谢52平台给了我学习的机会和动力,感谢初级题没有坑,除了一个debug判断{:301_998:}
后面的高级安卓题就不会了{:301_998:} hlrlqy 发表于 2020-2-9 13:33
所以onClick是在MainActivity注册的为什么还要hook找
emm 如果工程量大的话 就不能一眼看出来谁是谁了啊
当然对这题来说确实不用hook来找 繁华被爆护一次。 妈耶,我把第三题算法全搞出来了..... 羡慕、远处观看 你与明日 发表于 2020-2-9 06:20
妈耶,我把第三题算法全搞出来了.....
大佬求教程{:301_1003:}
(虽然可能看不懂) 原来如此啊,看完后恍然大悟 收听大佬了
恍然大悟,还是要认真学习 Android逆向一点都不会,要从零开始学了。 我也想到了用xposed来hookstring的几个方法,然而没装安卓编程环境,凉凉