唱吧登录分析
版本信息:
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}}
整个分析过程还是有不少问题,也没有办法把所有问题展示出来,只能说痛并快乐着……
仅作技术交流,请勿滥用
|