发新帖

唱吧登录算法分析

[复制链接]
49532 12
唱吧登录分析
版本信息:
package: name='com.changba' versionCode='631' versionName='6.3.2'
apk的assets目录
changba6321/assets/
├── test.aac
├── test.wav
├── test2.wav

其中的test.wav、test2.wav分别为arm及x86下使用,这里仅说明arm结构
test.wav是一个音频文件,可以正常播放,但是这个文件中还包含了一个jar包和一个so文件
在apk的KTVApplication.A中会将jar包和so文件释放出来,释放文件路径分别为/data/data/com.changba/app_dex/classes.jar、/data/data/com.changba/app_dex/test.wav
随后,KTVApplication.A会通过DexClassLoader加载上面的jar包,并通过反射机制调用jar包中GaoDLoader的setName以及test函数

public static void setName(File _name) {
        GaoDLoader.file = _name;
    }

    public static void test(String path) {
        Gao.jflag = 1024;
        try {
            System.load(String.valueOf(path) + "lib" + "secret." + "so");
        }
        catch(UnsatisfiedLinkError v0) {
            System.err.println("WARNING: Could not load library!");
        }

        try {
            if(GaoDLoader.file != null && (GaoDLoader.file.exists())) {
                System.load(GaoDLoader.file.getAbsolutePath());
                return;
            }

            System.load(String.valueOf(path) + GaoDLoader.name);
        }
        catch(UnsatisfiedLinkError v0) {
            System.err.println("WARNING: Could not load library!");
        }


在test函数中加载两个so,libsecret.so在当前版本中是没有的,第二个so即为前面释放出来的test.wav

test.wav的jni_onload中注册了com.changba.utils;Gao->o这个native函数

public static native String o(Gao this, String arg1);


在KTVApplication.A中还加载了一个common的so,这个so直接在apk对应的lib目录下,libcommon.so注册了com.changba.utils;JNIUtils中声明的5个native函数

    public static native String getCodeS(JNIUtils this, String arg1)

    public static native String getJiangKey(JNIUtils this, String arg1)

    public static native String getSecretKey(JNIUtils this, String arg1)

    public static native String getUserInfo(JNIUtils this, String arg1, String arg2, String arg3)

public static native boolean isCodeS(JNIUtils this, String arg1)


通过抓包可以发现,唱吧的登录过程如下:
1.从api.changba.com获取cookie,传递的参数
action=llogin&method=ktv&macaddress=09%3A00%3A27%3Aed%3A52%3A37&channelsrc=changba&bless=1&version=6.3.2&deviceid=09%3A00%3A27%3Aed%3A52%3A37
2. 向https://secure.changba.com发起请求,传递的参数
ac=llogin
ssid=     //由第一步中的cookie.check_code计算得到

//视情况而定是否需要,check_code如果以c22a1开头则不需要,否则需要为urlencode编码形式
captcha=  
//登录名
email=
//登录密码的md5
md5pwd=
//固定值
bless=1&channelsrc=changba
//设备的mac地址
macaddress=
deviceid=
//固定值
version=6.3.2
//生成的校验码
seret=
_usrinfo=

这里仅分析seret以及_usrinfo参数的获取,其他的都比较简单

这两个参数分别由com.changba.utils;JNIUtils->getSecretKey以及com.changba.utils;JNIUtils->getUserInfo生成


getSecretKey通过阅读libcommon.so,再结合动态调试,可以很快分析出来
.text:A8F9A390                 STR     R12, [R10,LR]
.text:A8F9A394                 MOV     R8, R12
.text:A8F9A398                 BL      memset
.text:A8F9A39C                 LDR     R1, =(aS - 0xA8F9A3B0)
.text:A8F9A3A0                 MOV     R2, R6
.text:A8F9A3A4                 ADD     R0, SP, #0x1050+s ; s
.text:A8F9A3A8                 ADD     R1, PC, R1      ; "~@%s^@"
.text:A8F9A3AC                 BL      sprintf         ; 拼接传递的参数
.text:A8F9A3B0                 ADD     R0, SP, #0x1050+s ; s
.text:A8F9A3B4                 BL      strlen
.text:A8F9A3B8                 ADD     R2, SP, #0x1050+var_1044
.text:A8F9A3BC                 MOV     R1, R0
.text:A8F9A3C0                 ADD     R0, SP, #0x1050+s
.text:A8F9A3C4                 BL      md5_hex         ; 获得md5
.text:A8F9A3C8                 ADD     R1, SP, #0x1050+src ; src
.text:A8F9A3CC                 MOV     R2, #0xA        ; n
.text:A8F9A3D0                 MOV     R0, SP          ; dest
.text:A8F9A3D4                 BL      strncpy         ; 取生成md5的5-15位
.text:A8F9A3D8                 LDR     R3, [R4]
.text:A8F9A3DC                 MOV     R2, R6

getUserInfo的分析则比较麻烦,getUserInfo中调用的是com.changba.utils;Gao->o,而这个函数的实际定义在前面释放的test.wav中,需要在test.wav的jni_onload调用中找到注册的地址
.text:00000AF8                 MOV     R1, R6
.text:00000AFC                 LDR     R3, [R3,#0x18]
.text:00000B00                 BLX     R3
.text:00000B04                 SUBS    R5, R0, #0
.text:00000B08                 BEQ     loc_B94
.text:00000B0C                 LDR     R3, [R4]
.text:00000B10                 MOV     R0, R4
.text:00000B14                 MOV     R1, R5
.text:00000B18                 LDR     R12, [R3,#0x35C]
.text:00000B1C                 ADD     R2, SP, #0x28+var_1C
.text:00000B20                 MOV     R3, #1
.text:00000B24                 BLX     R12             ; 调用env->registernatives
.text:00000B28                 CMP     R0, #0
.text:00000B2C                 BLT     loc_B94

动态跟踪这里的registernatives,再对照libdvm.so,

.text:0004DD0C
.text:0004DD0C loc_4DD0C                               ; CODE XREF: sub_4DBF0+ECj
.text:0004DD0C                                         ; sub_4DBF0+104j
.text:0004DD0C                 STRB.W          R11, [R0,#0x2C]
.text:0004DD10                 MOV             R1, R9 //这里的R1为注册函数的地址
.text:0004DD12                 BL              _Z15dvmUseJNIBridgeP6MethodPv ; dvmUseJNIBridge(Method *,void *)
.text:0004DD16                 ADD.W           R8, R8, #1
.text:0004DD1A                 ADDS            R7, #0xC
.text:0004DD1C
.text:0004DD1C loc_4DD1C                               ; CODE XREF: sub_4DBF0+42j
.text:0004DD1C                 CMP             R8, R10
.text:0004DD1E                 BLT             loc_4DC34
可以定位到Gao.o的函数偏移为8E0

这是它的伪代码
int __fastcall sub_8E0(int a1)
{
  int v1; // r3@1
  int v2; // r5@1
  const char *v3; // r0@1
  char *v4; // r6@1
  size_t v5; // r0@1
  int result; // r0@1
  char v7; // [sp+Ch] [bp-43Ch]@1
  char s; // [sp+2Ch] [bp-41Ch]@1
  char v9; // [sp+2Dh] [bp-41Bh]@1
  char v10; // [sp+2Eh] [bp-41Ah]@1
  char v11; // [sp+2Fh] [bp-419h]@1
  char v12; // [sp+30h] [bp-418h]@1
  char v13; // [sp+31h] [bp-417h]@1
  int v14; // [sp+42Ch] [bp-1Ch]@1

  v1 = *(_DWORD *)a1;
  v14 = _stack_chk_guard;
  v2 = a1;
  v3 = (const char *)(*(int (**)(void))(v1 + 676))();
  v4 = hello(v3);  //对传递参数中的字符挨个处理,得到一个和
  memset(&s, 0, 0x400u);
  s = 36;
  v12 = 36;
  v9 = 126;
  v10 = 33;
  v11 = 126;
  sprintf(&v13, "%d%sKcFO7ABaunKYFC8G%s", v4, "changba609", "gqoZhQ");//将上面得到的和拼接一个新的字符串
  v5 = strlen(&s);
  md5_hex(&s, v5, &v7);//取md5
  snprintf(&s, 0xBu, "%s", &v7);//取md5的10位
  result = (*(int (__fastcall **)(int, char *))(*(_DWORD *)v2 + 668))(v2, &s);
  if ( v14 != _stack_chk_guard )
    _stack_chk_fail(result);
  return result;
}

hello函数:
char *__fastcall hello(const char *a1)
{
  char *v1; // r0@1
  char *v2; // r4@1
  int v3; // r3@4
  int v4; // r5@6
  int v5; // r0@8
  int v6; // t1@9

  v1 = strstr(a1, "com/");
  v2 = v1;
  if ( v1 )
  {
    if ( strlen(v1) > 4 )
    {
      v3 = (unsigned __int8)v2[4];
      if ( v2[4] )
      {
        v4 = (int)(v2 + 4);
        v2 = 0;
        do
        {
          switch ( v3 )
          {
            case 48:
              v5 = sub_1F74(49);
              break;
            case 49:
              v5 = sub_BC4(49);
              break;
            case 50:
              v5 = sub_1F74(50);
              break;
            case 51:
              v5 = sub_30E4(51);
              break;
            case 52:
              v5 = sub_1EE4(52);
              break;
            case 53:
              v5 = sub_1434(53);
              break;
            case 54:
……
hello函数看起来很麻烦,其实只要注意几个特殊字符的处理就可以了,算法如下
def ca(c):
    if c==ord('0'):
        return 49*49
    elif c==ord('9'):
        return 59*59
    elif c==ord('?'):
        return 63*68*68
    elif c==ord('a'):
        return 100*100
    elif c==ord('z'):
        return 126*126
    elif c==ord('='):
        return 0
    else:
        return c*c

hello函数里应该是故意混杂了很多分支,目的是为了静态分析难以找到Gao.o

我已经在pc上用python实现了这个完整的登陆过程,代码还是不放了,以免不必要的麻烦
$ python changba6321.py
nikyname:n****
password:****
login success!
{u'errorcode': u'ok', u'result': {u'homeurl': u'http://changba.com/u/189***', u'vip': 0, u'ismember': 0, u'accesstoken2': None, u'accesstoken3': None, u'fansnum': 0, u'city': u'\u676d\u5dde', u'district': u'', u'title': u'', u'score': 0, u'location': u' ', u'latitude': u'0', u'memberlevel': u'', u'email': u'n****@changba.com', u'province': u'\u6d59\u6c5f', u'account_type': u'\u5531\u5427', u'agetag': u'90\u540e', u'accounttype3': u'\u6700\u6dd8', u'accounttype2': u'\u6700\u6dd8', u'viptitle': u'', u'memberid': u'189109611', u'astro': u'\u6469\u7faf\u5ea7', u'phone': u'18*****', u'birthday': u'1990-01-01', u'account_original': u'18****', u'headphoto': u'http://img.changba.com/cache/photo/4/4.jpg', u'userlevel': {u'starRank': u'10\u4e07\u540d\u4ee5\u5916', u'weekCost': -1, u'richLevel': 0, u'monthCost': -1, u'starLevel': 0, u'userid': u'101831092', u'pop': -1, u'richRank': u'10\u4e07\u540d\u4ee5\u5916', u'nextStarLevel': 0, u'cost': -1, u'monthPop': -1, u'richLevelName': u'', u'starLevelName': u'', u'nextRichLevel': 0}, u'accountid2': None, u'accountid3': None, u'nickname': u'n****', u'level': u'1', u'access_token': u'85a951aaf******a', u'gender': u'1', u'userid': u'101*****', u'longitude': u'0', u'token': u'T43f8d****a', u'signature': u'', u'accountid': u'n****', u'valid': 1, u'friendsnum': 0}}

整个分析过程还是有不少问题,也没有办法把所有问题展示出来,只能说痛并快乐着……

仅作技术交流,请勿滥用

举报 使用道具

回复

精彩评论11

水波摇曳    发表于 2015-8-23 16:49:11 | 显示全部楼层
感谢分析 赞一个~

举报 使用道具

回复 支持 反对
默小坑    发表于 2015-8-23 16:53:53 来自手机  | 显示全部楼层
感谢分享,赞两个

举报 使用道具

回复 支持 反对
十万个为什么    发表于 2015-8-23 19:19:53 | 显示全部楼层
菜鸟不懂

举报 使用道具

回复
freeparty    发表于 2015-8-23 23:13:18 | 显示全部楼层
我怎么记得在其他地方见过

点评

在看雪和这里发了  详情 回复 发表于 2015-8-24 12:26

举报 使用道具

回复 支持 反对
huluxia    发表于 2015-8-23 23:13:39 来自手机  | 显示全部楼层
感谢分享

举报 使用道具

回复
听鬼哥说故事    发表于 2015-8-24 09:27:47 | 显示全部楼层
不错,终于舍得放出来了哇~~赞一个~~~~

点评

期待鬼哥新作O(∩_∩)O~  详情 回复 发表于 2015-8-24 12:30

举报 使用道具

回复 支持 反对
huang4727603    发表于 2015-8-24 10:49:53 | 显示全部楼层
这个的加密我也看过,安卓下我看的时候有2个加密,一个是根据url +干扰 md5得到一个参数,然后是全部参数根据字符换算成数字,全部加起来,然后再进行md5,加密,得到另一个字段,当时没有找到这个代码,我还是一个个字符穷举过来算出来的userinfo="$~!~$"+num.to_s+"changba609KcFO7ABaunKYFC8GgqoZhQ"  #把num填入字符串最后生成新的字符串 当时写的算法是这样子的 不知道现在变了没,楼主研究的很仔细,比我更深入哈哈 学习

点评

恩,我看的这个版本也差不多这样,大神以后多分享啊O(∩_∩)O哈哈~  详情 回复 发表于 2015-8-24 12:28

举报 使用道具

回复 支持 反对
我为什么那么水    发表于 2015-8-24 12:26:29 | 显示全部楼层
freeparty 发表于 2015-8-23 23:13
我怎么记得在其他地方见过

在看雪和这里发了

举报 使用道具

回复 支持 反对
我为什么那么水    发表于 2015-8-24 12:28:31 | 显示全部楼层
huang4727603 发表于 2015-8-24 10:49
这个的加密我也看过,安卓下我看的时候有2个加密,一个是根据url +干扰 md5得到一个参数,然后是全部参数根 ...

恩,我看的这个版本也差不多这样,大神以后多分享啊O(∩_∩)O哈哈~

举报 使用道具

回复 支持 反对
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表