某大学在使用的一款教务管理系统手机app,为了方便学生查询成绩和选课。我在一次偶然逆向中找到严重漏洞,现在把整个分析流程记录下来。
app登录后进入appCenter的界面,内有九个图标
首先配置好手机和电脑端Fiddler的局域网下连接,等下获取手机的http和https协议。 登录完帐号后,点击“我的成绩”按钮,fd获取到了这个url请求。
发现这是一个GET包
http://ydjwapp.♦♦♦.edu.cn/login_sso.aspx?procode=002&type=1&choice=XS0205&uid=20162104♦♦♦♦♦&role=XS&key=C3840551F78846DFAABA84C9A81B4F06&time=1520081445
仔细观察这个请求构造参数,发现key这个参数格外引人注目,前面的无非是uid不同,choice之类的是成绩查询的代号,time这个参数和现行时间戳很像,但是位数不够,等会分析。
首先用APK改之理载入分析我们分析的这个app
搜索结果太多了,于是我换个思路,上面既然是GET请求,那么会不会有&key这个常量的保存呢?于是很巧,找到了一处我们要找的关键地方
通过改之理自带的jd-gui可以一键将smali反汇编代码翻译成参考的java源码,进入之后看到"&key"字符串在openURL()函数中,下面贴图部分为openURL()的部分源码,这里可以清楚地看到里面拼接的str1,可以判断基本上就是刚才提交url的地址了。因为里面包含了procode,choice,uid,key,time这些参数正是上面获取的url中get请求参数部分。
openURL()的全部代码
可以发现有两处相似代码,有点重复?根据if来判断,转折在str1,也就是前面url的get请求进来的procode参数,经过多次尝试,procode一直是"002",也就是上面那处。纵观整个反汇编后的apk文件工程来看,下面的是另一个是OA(Office Automation 办公自动化)系统的请求加密地方,也就是说这个app不只是单独为该高校设计的,而是通用型,或者说换了个包装和切换了下接口就拿来用了,属实有点太随意了吧。
private void openURL()
{
this.time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String str3 = this.url + "&" + "time=" + this.time;
Object localObject = getMapParam(this.url);
if (str3.indexOf("?") != -1)
{
str1 = (String)((Map)localObject).get("procode");
String str2 = (String)((Map)localObject).get("choice");
localObject = (String)((Map)localObject).get("uid");
if ((str1 != null) && (str1.equals("002")))
{
str3 = String.valueOf(new Date().getTime()).substring(0, 10);
Log.e("@@@@@@@@@@@@@@@@@@@@", String.valueOf(str3));
str1 = o.a(str1 + str2 + (String)localObject + "DAFF8EA19E6BAC86E040007F01004EA" + str3);
Log.e("@@@@@@@@@@@@@@@@@@@@", str1);
str1 = this.url + "&" + "key=" + str1 + "&" + "time=" + str3;
openUrlWithWebview(this.detailView, str1);
Log.e("WebModuleOaActivity", str1);
return;
}
if ((str1 != null) && (str1.equals("006")))
{
str1 = p.a(str1 + str2 + (String)localObject + this.time + "DAFF8EA19E6BAC86E040007F01004EA");
Log.e("snstimepublickey", str1);
str1 = new StringBuilder(String.valueOf(this.url)).append("&").append("key=").append(str1).append("&").append("time=").append(this.time).toString() + "&tgc=" + t.a(getApplicationContext(), "catgc", "tgc");
openUrlWithWebview(this.detailView, str1);
Log.e("WebModuleOaActivity", str1);
return;
}
str1 = str3 + "&tgc=" + t.a(getApplicationContext(), "catgc", "tgc");
openUrlWithWebview(this.detailView, str1);
Log.e("WebModuleOaActivity", str1);
return;
}
String str1 = this.url + "&tgc=" + t.a(getApplicationContext(), "catgc", "tgc");
openUrlWithWebview(this.detailView, str1);
Log.e("WebModuleOaActivity", str1);
}
既然玩安卓逆向,当然少不了Xposed框架了,它可是我们挂钩子的好工具
通过上面的图不难发现,第一个箭头指向的o.a()方法是改key参数构造的最直接加密方法,二话不说,点击这个带下划线的a进入a()方法,看看里面长什么样。
里面调用了内部类的a()方法和重载a()方法以及b()方法,具体我就不贴图了,目的是改变改静态类的一些全局参数,然后经过后面的计算返回解密后的结果赋值给str1,即key的值。
判断是发送url请求,其中有最后拼接完整的字符串,即第二个传参paramString,准备下一步拦截进行验证
2.利用Xposed框架拦截O.a()方法的传参和返回值和openUrlWithWebview()的传参
static int i = 1;
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if(!loadPackageParam.packageName.equals("com.♦♦♦♦♦.jw")){
return;
}
XposedBridge.log("Load app:----->"+loadPackageParam.packageName+" 加载完毕");
findAndHookMethod("com.zfsoft.core.d.o", loadPackageParam.classLoader, "a", String.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("----第"+(i++)+"次拦截----");
XposedBridge.log("a方法 传入参数:"+param.args[0]);
XposedBridge.log("a方法 返回参数:"+param.getResult());
}
});
findAndHookMethod("com.zfsoft.webmodule.controller.WebModuleOaFun", loadPackageParam.classLoader, "openUrlWithWebview", WebView.class, String.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("openUrlWithWebview方法 传入参数:"+param.args[1]);
}
});
}
第一次拦截参数分析
参数 | 值 |
---|---|
str1 | 002 |
str1 | XS0205 |
(String)localObject | 20162104♦♦♦♦♦ |
const str | DAFF8EA19E6BAC86E040007F01004EA |
time | 1520088144 |
----->com.♦♦♦♦.jw 加载完毕
----第1次拦截----
a方法 传入参数:002XS020520162104♦♦♦♦♦DAFF8EA19E6BAC86E040007F01004EA1520088144
a方法 返回参数:ECC9D8F62A45091ED3EA35B691105318
openUrlWithWebview方法 传入参数:
http://ydjwapp.♦♦♦.edu.cn/login_sso.aspx?procode=002&type=1&choice=XS0205&uid=20162104♦♦♦♦♦&role=XS&key=ECC9D8F62A45091ED3EA35B691105318&time=1520088144
----第2次拦截----
a方法 传入参数:002XS020820162104♦♦♦♦♦DAFF8EA19E6BAC86E040007F01004EA1520088165
a方法 返回参数:818348171E471F392C23CBFF99EA3E36
openUrlWithWebview方法 传入参数:
http://ydjwapp.♦♦♦.edu.cn/login_sso.aspx?procode=002&type=1&choice=XS0208&uid=20162104♦♦♦♦♦&role=XS&key=818348171E471F392C23CBFF99EA3E36&time=1520088165
----第3次拦截----
a方法 传入参数:002XS020420162104♦♦♦♦♦DAFF8EA19E6BAC86E040007F01004EA1520088169
a方法 返回参数:1CE6D9F428DF5294E66D88C0653574BC
openUrlWithWebview方法 传入参数:
http://ydjwapp.♦♦♦.edu.cn/login_sso.aspx?procode=002&type=1&choice=XS0204&uid=20162104♦♦♦♦♦&role=XS&key=1CE6D9F428DF5294E66D88C0653574BC&time=1520088169
借用上面第一次a方法拦截到的参数
002XS020520162104♦♦♦♦♦DAFF8EA19E6BAC86E040007F01004EA1520088144
可知传入这个方法的参数包含5个字符串的拼接,进入到o这个类中的a()加密运算后返回给str1,后面str1再拼接成完整的GET请求的url。
2.用Elipse模拟a()方法
来到com.zfsoft.core.d.o这个类(这个就是上面a()方法所在类),于是我们在Elipse中新建类,命名为o,拷贝通过APK改之理自带的jd-gui转换过来过来的java源码,会发现很多处不合理的代码,如:(观察如下return位置)
if (paramInt >= j)
{
a(e, paramArrayOfByte, i, 0, j);
a(e);
i = j;
if (i + 63 >= paramInt)
{
j = i;
i = k;
}
}
for (;;)
{
a(e, paramArrayOfByte, i, j, paramInt - j);
return; //这里return显然不合理,后面的操作会影响结果的。
a(arrayOfByte, paramArrayOfByte, 0, i, 64);
a(arrayOfByte);
i += 64;
break;
j = 0;
}
这里我是用到了JEB 1.5强大的反编译工具, 上面的代码变成了如下:
if(paramInt >= j) {
o.a(o.e, paramArrayOfByte, i, 0, j);
o.a(o.e);
for(i = j; i + 63 < paramInt; i += 64) {
o.a(arrayOfByte, paramArrayOfByte, 0, i, 64);
o.a(arrayOfByte);
}
}
else {
int v10 = i;
i = 0;
k = v10;
}
o.a(o.e, paramArrayOfByte, k, i, paramInt - i);
可读性变强了很多,其实要修改的地方还有别的三处,就不一一陈列了,当时调试了一个下午,才改完的。
public static void main(String[] args) {
System.out.println(o.a("002XS020520162104♦♦♦♦♦DAFF8EA19E6BAC86E040007F01004EA1520088144"));
}
前面记录的数据:
a方法 传入参数:002XS020520162104♦♦♦♦♦DAFF8EA19E6BAC86E040007F01004EA1520088144 a方法 返回参数:ECC9D8F62A45091ED3EA35B691105318 测试成功截图:
通过学号和当前时间戳取前十位以及前面的固定参数做一个key的加密计算,拼接url提交网页即可访问到正确的结果。
明确关键位置,Hook拦截传参,以及加密结果返回值,能为我们更好分析部分参数的生成以及提交GET请求参数的拼接。