前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >某教务管理系统APP逆向分析之协议漏洞

某教务管理系统APP逆向分析之协议漏洞

作者头像
安恒网络空间安全讲武堂
发布2019-09-29 11:24:04
1K0
发布2019-09-29 11:24:04
举报

0X01前言

样本来源

某大学在使用的一款教务管理系统手机app,为了方便学生查询成绩和选课。我在一次偶然逆向中找到严重漏洞,现在把整个分析流程记录下来。

准备工具
  1. Fiddler
  2. APK改之理
  3. JEB 1.5
  4. PC端安卓模拟器(我用的海马模拟器)
  5. XposedBridgeApi-54.jar
  6. Eclipse

0X02 功能协议分析

 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

分析url的参数

 仔细观察这个请求构造参数,发现key这个参数格外引人注目,前面的无非是uid不同,choice之类的是成绩查询的代号,time这个参数和现行时间戳很像,但是位数不够,等会分析。

0x03深入APK

首先用APK改之理载入分析我们分析的这个app

参数key的寻找

搜索结果太多了,于是我换个思路,上面既然是GET请求,那么会不会有&key这个常量的保存呢?于是很巧,找到了一处我们要找的关键地方

进入这处的java源码

通过改之理自带的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);
}

0x04HOOK关键函数

 既然玩安卓逆向,当然少不了Xposed框架了,它可是我们挂钩子的好工具

1.找到关键函数
a()方法

通过上面的图不难发现,第一个箭头指向的o.a()方法是改key参数构造的最直接加密方法,二话不说,点击这个带下划线的a进入a()方法,看看里面长什么样。

 里面调用了内部类的a()方法和重载a()方法以及b()方法,具体我就不贴图了,目的是改变改静态类的一些全局参数,然后经过后面的计算返回解密后的结果赋值给str1,即key的值。

openUrlWithWebview()方法

  判断是发送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]);
        }
    });

}
3.在模拟器中测试查看拦截数据

第一次拦截参数分析

参数

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

0x05还原核心加密算法

1.明确加密方法a()

借用上面第一次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);

可读性变强了很多,其实要修改的地方还有别的三处,就不一一陈列了,当时调试了一个下午,才改完的。

3.执行Main函数调用O.a()方法
public static void main(String[] args) {
  System.out.println(o.a("002XS020520162104♦♦♦♦♦DAFF8EA19E6BAC86E040007F01004EA1520088144"));
}

前面记录的数据:

a方法 传入参数:002XS020520162104♦♦♦♦♦DAFF8EA19E6BAC86E040007F01004EA1520088144 a方法 返回参数:ECC9D8F62A45091ED3EA35B691105318 测试成功截图:

通过学号和当前时间戳取前十位以及前面的固定参数做一个key的加密计算,拼接url提交网页即可访问到正确的结果。

0x06总结

明确关键位置,Hook拦截传参,以及加密结果返回值,能为我们更好分析部分参数的生成以及提交GET请求参数的拼接。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0X01前言
  • 0X02 功能协议分析
  • 0x03深入APK
  • 0x04HOOK关键函数
  • 0x05还原核心加密算法
  • 0x06总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档